Ты Java программист и устал серфить интернет в поисках вакансий? Тяжело найти ту самую работу, которая будет соответствовать и отвечать требованиям твоего уровня?
Java Job - канал, который поможет решить эти вопросы
На канале ты найдешь:
• свежие и актуальные вакансии, с достойными зарплатами и условиями труда
• стажировки, бесплатные курсы, работа для джунов, мидлов и сеньеров по всему миру
• примеры вопросов с собеседований, интересные задачи и обучающий материал для повышения скилла
Подписывайся на наш канал, цени свое время!
Java Job - канал, который поможет решить эти вопросы
На канале ты найдешь:
• свежие и актуальные вакансии, с достойными зарплатами и условиями труда
• стажировки, бесплатные курсы, работа для джунов, мидлов и сеньеров по всему миру
• примеры вопросов с собеседований, интересные задачи и обучающий материал для повышения скилла
Подписывайся на наш канал, цени свое время!
👍1
Чем анонимный внутренний класс отличается от лямбды?
Лямбда-выражение имеет более легковесный синтаксис. Не нужно явно указывать тип функционального интерфейса, который лямбда реализует – он автоматически выведется из контекста. Лямбда-выражения добавлены в язык в первую очередь как синтаксический сахар.
С другой стороны, у класса есть поля. Экземпляр анонимного класса сохраняет свое состояние между вызовами, и меняет его при необходимости. Для лямбды доступен лишь захват и effectively final использование внешних переменных.
Лямбдой реализуется только функциональный интерфейс. Функциональный интерфейс – это тип с единственным абстрактным методом. Анонимным классом же можно расширить любой расширяемый класс или реализовать интерфейс с любым количеством абстрактных методов.
Анонимный класс создает новый скоуп, лямбда работает в текущем. Это значит, что объявление переменной с именем, которое уже используется снаружи, в лямбде вызовет ошибку компиляции «variable is already defined», в анонимном классе скроет (shadowing) внешнюю переменную.
С точки зрения реализации JVM, для лямбды не создается дополнительного .class файла, как это происходит для анонимного класса. Соответственно, не происходит и обычной загрузки и верификации класса. Вместо этого используется механизм
Лямбда-выражение имеет более легковесный синтаксис. Не нужно явно указывать тип функционального интерфейса, который лямбда реализует – он автоматически выведется из контекста. Лямбда-выражения добавлены в язык в первую очередь как синтаксический сахар.
С другой стороны, у класса есть поля. Экземпляр анонимного класса сохраняет свое состояние между вызовами, и меняет его при необходимости. Для лямбды доступен лишь захват и effectively final использование внешних переменных.
Лямбдой реализуется только функциональный интерфейс. Функциональный интерфейс – это тип с единственным абстрактным методом. Анонимным классом же можно расширить любой расширяемый класс или реализовать интерфейс с любым количеством абстрактных методов.
Анонимный класс создает новый скоуп, лямбда работает в текущем. Это значит, что объявление переменной с именем, которое уже используется снаружи, в лямбде вызовет ошибку компиляции «variable is already defined», в анонимном классе скроет (shadowing) внешнюю переменную.
С точки зрения реализации JVM, для лямбды не создается дополнительного .class файла, как это происходит для анонимного класса. Соответственно, не происходит и обычной загрузки и верификации класса. Вместо этого используется механизм
invokedynamic
и класс генерируется на лету с помощью LambdaMetafactory
. Так что лямбда-выражения обычно работают быстрее. Внутренности реализации подробно разобраны например в этой статье.👍10
Сегодня паттерн Посетитель в Java уже не нужен – лучше использовать переключатели паттернов
В современном языке Java паттерн Посетитель (Visitor) уже не нужен. Он отлично компенсируется использованием запечатанных типов и переключателей, использующих сопоставление с шаблоном – в таком случае те же цели достигаются проще и меньшим объемом кода. Далее
В современном языке Java паттерн Посетитель (Visitor) уже не нужен. Он отлично компенсируется использованием запечатанных типов и переключателей, использующих сопоставление с шаблоном – в таком случае те же цели достигаются проще и меньшим объемом кода. Далее
👍3👎1
Что такое Reflection и как его использовать?
Reflection, рефлексия – это средства манипуляции данными на основе знания о структуре классов этих данных, инструменты метапрограммирования.
Класс Class<T> используется как точка входа в мир рефлекшена. Его экземпляры предоставляют саму метаинформацию о содержимом класса и основные методы для работы с ним. Все классы относящиеся Java Reflection находятся в пакетах
Экземпляр класса
🔘 Литералом
🔘 Статическим фабричным методом
🔘 Методом getClass() экземпляров класса.
Использование Reflection API медленное и небезопасное. Оно позволяет ломать инвариантность состояний экземпляра, нарушать инкапсуляцию, и даже менять финальные поля.
Использовать рефлексию естественно в тестовом коде, в инструментах разработки, в фреймворках (особенно в связке с runtime-аннотациями). Рефлекшн в ординарном бизнес-коде обычно говорит о больших проблемах проектирования.
Нередко на интервью просят продемонстрировать пример использования рефлекшна. Один из самых близких для backend-разработчика примеров – инициализация классов-конфигураций в Spring Framework. Фреймворк с помощью рефлекшна сканирует внутренности таких классов. Поля и методы, помеченные специальными аннотациями, воспринимаются как объявления элементов экосистемы фреймворка.
#Классы
Reflection, рефлексия – это средства манипуляции данными на основе знания о структуре классов этих данных, инструменты метапрограммирования.
Класс Class<T> используется как точка входа в мир рефлекшена. Его экземпляры предоставляют саму метаинформацию о содержимом класса и основные методы для работы с ним. Все классы относящиеся Java Reflection находятся в пакетах
java.lang
и java.lang.reflect
.Экземпляр класса
Class
можно получить тремя способами:🔘 Литералом
.class
;🔘 Статическим фабричным методом
Class.forName()
;🔘 Методом getClass() экземпляров класса.
Использование Reflection API медленное и небезопасное. Оно позволяет ломать инвариантность состояний экземпляра, нарушать инкапсуляцию, и даже менять финальные поля.
Использовать рефлексию естественно в тестовом коде, в инструментах разработки, в фреймворках (особенно в связке с runtime-аннотациями). Рефлекшн в ординарном бизнес-коде обычно говорит о больших проблемах проектирования.
Нередко на интервью просят продемонстрировать пример использования рефлекшна. Один из самых близких для backend-разработчика примеров – инициализация классов-конфигураций в Spring Framework. Фреймворк с помощью рефлекшна сканирует внутренности таких классов. Поля и методы, помеченные специальными аннотациями, воспринимаются как объявления элементов экосистемы фреймворка.
#Классы
👍3👎1😱1
На какие области делится память JVM?
Следует помнить, что это внутренние особенности HotSpot (и её opensource-версии OpenJDK). В других виртуальных машинах (например в Android) всё может быть абсолютно по-другому. Области-поколения кучи вообще зависят от используемого алгоритма сборки мусора, и могут отличаться в рамках одной и той же реализации виртуальной машины. Как было сказано в предыдущих постах, некоторые сборщики не пользуются понятием поколений совсем.
Stack – место под примитивы и ссылки на объекты (но не сами объекты). Хранит локальные переменные и возвращаемые значения функций. Здесь же хранятся ссылки на объекты пока те конструируются. Все данные в стеке – GC roots. Освобождается сразу на выходе из функции. Принадлежит потоку, размер по-умолчанию указывается параметром виртуальной машины
PermGen – В этой области хранятся загруженные классы (экземпляры класса Class<T>). Здесь же с Java 7 хранится пул строк. Изначально размера
Metaspace – с Java 8 заменяет permanent generation. Отличие в том, что по умолчанию metaspace ограничен только размерами доступной на машине памяти, но так же как PermGen может быть ограничен, параметром
Heap – куча, вся managed-память, в которой хранятся все пользовательские объекты. Все следующие разделы – части кучи. Параметры
Eden, New Generation, Old Generation и другие – специфичные для сборщика мусора части кучи, поколения. Могут быть разные, но общий подход сохраняется: долго живущий объект постепенно двигается во всё более старое поколение; сборка мусора в разных поколениях происходит раздельно; чем поколение старше, тем сборка в нём реже, но и дороже. Подробнее.
Хотя устройство памяти – это детали реализации виртуальной машины, для Java-разработчика знания о них несут практическую пользу. Эти знания необходимы для передачи правильных значений параметров JVM, что в свою очередь спасает от просадок производительности GC и остановок с
#JVM
Следует помнить, что это внутренние особенности HotSpot (и её opensource-версии OpenJDK). В других виртуальных машинах (например в Android) всё может быть абсолютно по-другому. Области-поколения кучи вообще зависят от используемого алгоритма сборки мусора, и могут отличаться в рамках одной и той же реализации виртуальной машины. Как было сказано в предыдущих постах, некоторые сборщики не пользуются понятием поколений совсем.
Stack – место под примитивы и ссылки на объекты (но не сами объекты). Хранит локальные переменные и возвращаемые значения функций. Здесь же хранятся ссылки на объекты пока те конструируются. Все данные в стеке – GC roots. Освобождается сразу на выходе из функции. Принадлежит потоку, размер по-умолчанию указывается параметром виртуальной машины
-Xss
, но при создании потока программно можно указать отличное значение. Подробнее.PermGen – В этой области хранятся загруженные классы (экземпляры класса Class<T>). Здесь же с Java 7 хранится пул строк. Изначально размера
-XX:PermSize
, растет динамически до -XX:MaxPermSize
. Не считается частью кучи.Metaspace – с Java 8 заменяет permanent generation. Отличие в том, что по умолчанию metaspace ограничен только размерами доступной на машине памяти, но так же как PermGen может быть ограничен, параметром
-XX:MaxMetaspaceSize
.Heap – куча, вся managed-память, в которой хранятся все пользовательские объекты. Все следующие разделы – части кучи. Параметры
-Xms
, -Xmn
и -Xmx
устанавливают начальный, минимальный и максимальный размеры хипа соответственно.Eden, New Generation, Old Generation и другие – специфичные для сборщика мусора части кучи, поколения. Могут быть разные, но общий подход сохраняется: долго живущий объект постепенно двигается во всё более старое поколение; сборка мусора в разных поколениях происходит раздельно; чем поколение старше, тем сборка в нём реже, но и дороже. Подробнее.
Хотя устройство памяти – это детали реализации виртуальной машины, для Java-разработчика знания о них несут практическую пользу. Эти знания необходимы для передачи правильных значений параметров JVM, что в свою очередь спасает от просадок производительности GC и остановок с
OutOfMemoryError
. #JVM
👍4
▶️ Более 70 000 учеников уже присоединились к beONmax!
https://beonmax.com
Изучайте самые популярные онлайн профессии на практике. Более 50+ курсов по верстке и созданию сайтов, программированию, базам данных, мобильной разработке, дизайну, продвижению и др.
🔹 HTML / CSS
🔹 JavaScript, React JS
🔹 PHP / MySQL
🔹 Python, Django
🔹 Java, Kotlin, Android, C#
🔹 WordPress
🔹 Figma, Photoshop, Illustrator
🔹 Реклама Yandex, Google, VK
и многое другое…
Удобно и выгодно ⏩ Обучайтесь на любом курсе по единой подписке - от 90 руб. в месяц!
Вы получите:
☑️ Обучение от экспертов-практиков с реальным опытом
☑️ Десятки крутых проектов в свое портфолио
☑️ Сертификаты по итогам каждого курса
☑️ Рекомендации по трудоустройству или поиску заказов
☑️ Вопросы и обмен опытом в сообществе
☑️ Доступ онлайн 24/7
Отзывы реальных учеников, прошедших обучение на beONmax 🚀
▶️ Начать легко и доступно каждому, даже если вы новичок. Просто регистрируйтесь и проходите первые открытые уроки — Присоединяйтесь к beONmax!
https://beonmax.com
Изучайте самые популярные онлайн профессии на практике. Более 50+ курсов по верстке и созданию сайтов, программированию, базам данных, мобильной разработке, дизайну, продвижению и др.
🔹 HTML / CSS
🔹 JavaScript, React JS
🔹 PHP / MySQL
🔹 Python, Django
🔹 Java, Kotlin, Android, C#
🔹 WordPress
🔹 Figma, Photoshop, Illustrator
🔹 Реклама Yandex, Google, VK
и многое другое…
Удобно и выгодно ⏩ Обучайтесь на любом курсе по единой подписке - от 90 руб. в месяц!
Вы получите:
☑️ Обучение от экспертов-практиков с реальным опытом
☑️ Десятки крутых проектов в свое портфолио
☑️ Сертификаты по итогам каждого курса
☑️ Рекомендации по трудоустройству или поиску заказов
☑️ Вопросы и обмен опытом в сообществе
☑️ Доступ онлайн 24/7
Отзывы реальных учеников, прошедших обучение на beONmax 🚀
▶️ Начать легко и доступно каждому, даже если вы новичок. Просто регистрируйтесь и проходите первые открытые уроки — Присоединяйтесь к beONmax!
Какие промежуточные методы работы со стримами вы знаете?
filter() отфильтровывает записи, возвращая только записи, соответствующие условию;
skip() позволяет пропустить определённое количество элементов в начале;
distinct() возвращает стрим без дубликатов (для метода equals());
map() преобразует каждый элемент;
peek() возвращает тот же стрим, применяя к каждому элементу функцию;
limit() позволяет ограничить выборку определенным количеством первых элементов;
sorted() позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator;
mapToInt(), mapToDouble(), mapToLong() - аналоги map() возвращающие стрим числовых примитивов;
flatMap(), flatMapToInt(), flatMapToDouble(), flatMapToLong() - похожи на map(), но могут создавать из одного элемента несколько.
Для числовых стримов дополнительно доступен метод mapToObj(), который преобразует числовой стрим обратно в объектный.
filter() отфильтровывает записи, возвращая только записи, соответствующие условию;
skip() позволяет пропустить определённое количество элементов в начале;
distinct() возвращает стрим без дубликатов (для метода equals());
map() преобразует каждый элемент;
peek() возвращает тот же стрим, применяя к каждому элементу функцию;
limit() позволяет ограничить выборку определенным количеством первых элементов;
sorted() позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator;
mapToInt(), mapToDouble(), mapToLong() - аналоги map() возвращающие стрим числовых примитивов;
flatMap(), flatMapToInt(), flatMapToDouble(), flatMapToLong() - похожи на map(), но могут создавать из одного элемента несколько.
Для числовых стримов дополнительно доступен метод mapToObj(), который преобразует числовой стрим обратно в объектный.
👍8
Промежуточные операции Stream API
Все методы Stream API можно разделить на две группы: промежуточные и терминальные (конечные). Промежуточные операции следует воспринимать как «отложенные», т.е. они не меняют сами данные, а только задают правила их изменения. А терминальные как раз инициируют всю цепочку преобразований и возвращают модифицированные данные.
Рассмотрим промежуточные операции. Все промежуточные операции возвращают типизированный интерфейс Stream<>.
Все методы Stream API можно разделить на две группы: промежуточные и терминальные (конечные). Промежуточные операции следует воспринимать как «отложенные», т.е. они не меняют сами данные, а только задают правила их изменения. А терминальные как раз инициируют всю цепочку преобразований и возвращают модифицированные данные.
Рассмотрим промежуточные операции. Все промежуточные операции возвращают типизированный интерфейс Stream<>.
Telegraph
Промежуточные операции Stream API
Все методы Stream API можно разделить на две группы: промежуточные и терминальные (конечные). Промежуточные операции следует воспринимать как «отложенные», т.е. они не меняют сами данные, а только задают правила их изменения. А терминальные как раз инициируют…
👍6
Как объявить переменное количество аргументов метода?
Для этого используется аргумент-массив. В нем может находиться любое количество элементов. Еще с Java 5 для этого случая добавился синтаксический сахар: Variable-length argument (vararg). Три точки
На уровне байткода применение массива и варарга не отличаются: vararg-параметр
Чтобы избежать неоднозначностей, на vararg наложено ограничение: им может быть только один последний аргумент.
Vararg, как массив, может быть пустым. Иногда это приводит к неочевидному поведению. Допустим, имеем две перегрузки метода с аргументами
Когда типом варарга используется generic-параметр, возникает warning «Possible heap pollution from parameterized vararg type». Вам нужно убедиться, что вы понимаете в чем этот риск, что ваш код не приводит к heap pollution, и уведомить об этом компилятор аннотацией
#Язык
Для этого используется аргумент-массив. В нем может находиться любое количество элементов. Еще с Java 5 для этого случая добавился синтаксический сахар: Variable-length argument (vararg). Три точки
...
ставятся между типом и именем переменной, и становится можно передать любое количество аргументов, не упаковывая их в массив. На уровне байткода применение массива и варарга не отличаются: vararg-параметр
Foo...
превращается в параметр-массив Foo[]
, на этапе вызова подставляется неявное инстанцирование и заполнение массива.Чтобы избежать неоднозначностей, на vararg наложено ограничение: им может быть только один последний аргумент.
Vararg, как массив, может быть пустым. Иногда это приводит к неочевидному поведению. Допустим, имеем две перегрузки метода с аргументами
int...
и float...
. Вызов такого метода без параметров попадает в вариант с int
, как с более специфичным типом. Наличие же перегрузки с несовместимыми типами, например int...
и boolean...
, приводит при вызове к ошибке компиляции «Ambiguous method call».Когда типом варарга используется generic-параметр, возникает warning «Possible heap pollution from parameterized vararg type». Вам нужно убедиться, что вы понимаете в чем этот риск, что ваш код не приводит к heap pollution, и уведомить об этом компилятор аннотацией
@SafeVarargs
.#Язык
👍5
Подборка каналов для IT специалистов 🎯
Программирование C++📌
https://t.me/cpp_lib Библиотека C/C++ разработчика
https://t.me/cpp_knigi Книги для программистов C/C++
Программирование Python 📌
https://t.me/pythonofff Python академия. Учи Python быстро и легко🐍
https://t.me/BookPython Библиотека Python разработчика
https://t.me/python_real Python подборки на русском и английском
https://t.me/python_360 Книги по Python Rus
Java разработка 📌
https://t.me/BookJava Библиотека Java разработчика
https://t.me/java_360 Книги по Java Rus
GitHub Сообщество 📌
https://t.me/Githublib Интересное из GitHub
CodePen 📌
https://t.me/codepen_1 Сообщество пользователей CodePen
Базы данных (Data Base) 📌
https://t.me/database_info Все про базы данных
Мобильная разработка: iOS, Android 📌
https://t.me/developer_mobila Мобильная разработка
Фронтенд разработка 📌
https://t.me/frontend_1 Подборки для frontend разработчиков
Разработка игр 📌
https://t.me/game_devv Все о разработке игр
Вакансии 📌
https://t.me/sysadmin_rabota Системный Администратор
https://t.me/progjob Вакансии в IT
Чат программистов📌
https://t.me/developers_ru
Библиотеки 📌
https://t.me/book_for_dev Книги для программистов Rus
https://t.me/programmist_of Книги по программированию
https://t.me/proglb Библиотека программиста
https://t.me/bfbook Книги для программистов
БигДата, машинное обучение 📌
https://t.me/bigdata_1 Data Science, Big Data, Machine Learning, Deep Learning
Программирование 📌
https://t.me/bookflow Лекции, видеоуроки, доклады с IT конференций
https://t.me/coddy_academy Полезные советы по программированию
QA, тестирование 📌
https://t.me/testlab_qa Библиотека тестировщика
Шутки программистов 📌
https://t.me/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://t.me/thehaking Канал о кибербезопасности
Книги, статьи для дизайнеров 📌
https://t.me/ux_web Статьи, книги для дизайнеров
https://t.me/arhitekturamira World Architecture
Системное администрирование 📌
https://t.me/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
https://t.me/sysadminof Книги для админов, полезные материалы
https://t.me/i_odmin Все для системного администратора
https://t.me/i_odmin_book Библиотека Системного Администратора
https://t.me/i_odmin_chat Чат системных администраторов
https://t.me/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
Английский 📌
https://t.me/UchuEnglish Английский с нуля
Математика 📌
https://t.me/Pomatematike Канал по математике
Арбитраж трафика 📌
https://t.me/partnerochkin CPA и арбитраж трафика
Крипта 📌
https://t.me/bitkoinoff Новости криптовалют
Метавселенная, GameFi, Crypto 📌
https://t.me/metaverse360
DeepFake 📌
https://t.me/deepfakenow Публикуем deepfake видео
Мир технологий 📌
https://t.me/mir_teh Видео из мира технологий
Excel лайфхак📌
https://t.me/Excel_lifehack
Программирование C++📌
https://t.me/cpp_lib Библиотека C/C++ разработчика
https://t.me/cpp_knigi Книги для программистов C/C++
Программирование Python 📌
https://t.me/pythonofff Python академия. Учи Python быстро и легко🐍
https://t.me/BookPython Библиотека Python разработчика
https://t.me/python_real Python подборки на русском и английском
https://t.me/python_360 Книги по Python Rus
Java разработка 📌
https://t.me/BookJava Библиотека Java разработчика
https://t.me/java_360 Книги по Java Rus
GitHub Сообщество 📌
https://t.me/Githublib Интересное из GitHub
CodePen 📌
https://t.me/codepen_1 Сообщество пользователей CodePen
Базы данных (Data Base) 📌
https://t.me/database_info Все про базы данных
Мобильная разработка: iOS, Android 📌
https://t.me/developer_mobila Мобильная разработка
Фронтенд разработка 📌
https://t.me/frontend_1 Подборки для frontend разработчиков
Разработка игр 📌
https://t.me/game_devv Все о разработке игр
Вакансии 📌
https://t.me/sysadmin_rabota Системный Администратор
https://t.me/progjob Вакансии в IT
Чат программистов📌
https://t.me/developers_ru
Библиотеки 📌
https://t.me/book_for_dev Книги для программистов Rus
https://t.me/programmist_of Книги по программированию
https://t.me/proglb Библиотека программиста
https://t.me/bfbook Книги для программистов
БигДата, машинное обучение 📌
https://t.me/bigdata_1 Data Science, Big Data, Machine Learning, Deep Learning
Программирование 📌
https://t.me/bookflow Лекции, видеоуроки, доклады с IT конференций
https://t.me/coddy_academy Полезные советы по программированию
QA, тестирование 📌
https://t.me/testlab_qa Библиотека тестировщика
Шутки программистов 📌
https://t.me/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://t.me/thehaking Канал о кибербезопасности
Книги, статьи для дизайнеров 📌
https://t.me/ux_web Статьи, книги для дизайнеров
https://t.me/arhitekturamira World Architecture
Системное администрирование 📌
https://t.me/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
https://t.me/sysadminof Книги для админов, полезные материалы
https://t.me/i_odmin Все для системного администратора
https://t.me/i_odmin_book Библиотека Системного Администратора
https://t.me/i_odmin_chat Чат системных администраторов
https://t.me/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
Английский 📌
https://t.me/UchuEnglish Английский с нуля
Математика 📌
https://t.me/Pomatematike Канал по математике
Арбитраж трафика 📌
https://t.me/partnerochkin CPA и арбитраж трафика
Крипта 📌
https://t.me/bitkoinoff Новости криптовалют
Метавселенная, GameFi, Crypto 📌
https://t.me/metaverse360
DeepFake 📌
https://t.me/deepfakenow Публикуем deepfake видео
Мир технологий 📌
https://t.me/mir_teh Видео из мира технологий
Excel лайфхак📌
https://t.me/Excel_lifehack
👍1
Как в лямбде изменить внешнюю локальную переменную?
Это нельзя сделать в лоб. Такой код не скомпилируется, потому что захваченная локальная переменная обязана быть effectively final. Такое требование исходит из следующих причин.
Локальная переменная хранится на стеке, а значит время ее жизни в отличие от долгоживущих элементов хипа ограничено скоупом и текущим потоком. Но экземпляр лямбды, захвативший эту переменную, мог бы быть передан наружу и использован для доступа к этой переменной из другого потока и после выхода из метода.
Эта проблема решается тем, что в лямбду копируется значение локальной переменной. Такая копия живет независимо, возможно дольше оригинала. Но это решение приведет к сложному поведению из-за возможности работы с неактуальным значением – копия и оригинал станут двумя разными переменными. Поэтому значение должно быть вечно актуально – неизменяемо.
Поля экземпляра менять можно, потому что захваченной переменной в этом случае выступает effectively final значение
Если локальную переменную всё же хочется изменить, решение очевидно – поместить её в кучу. Для этого нужно использовать любого рода обертку: одноэлементный массив, объект-atomic, специально созданный класс с этой переменной как полем.
Хак с оберткой решает проблему времени жизни и даёт коду скомпилироваться, но возвращает проблему сложности поведения. Если среда многопоточная, то вероятно порядок операций с этой переменной придется синхронизировать вручную.
#Язык #Многопоточность
Это нельзя сделать в лоб. Такой код не скомпилируется, потому что захваченная локальная переменная обязана быть effectively final. Такое требование исходит из следующих причин.
Локальная переменная хранится на стеке, а значит время ее жизни в отличие от долгоживущих элементов хипа ограничено скоупом и текущим потоком. Но экземпляр лямбды, захвативший эту переменную, мог бы быть передан наружу и использован для доступа к этой переменной из другого потока и после выхода из метода.
Эта проблема решается тем, что в лямбду копируется значение локальной переменной. Такая копия живет независимо, возможно дольше оригинала. Но это решение приведет к сложному поведению из-за возможности работы с неактуальным значением – копия и оригинал станут двумя разными переменными. Поэтому значение должно быть вечно актуально – неизменяемо.
Поля экземпляра менять можно, потому что захваченной переменной в этом случае выступает effectively final значение
this
.Если локальную переменную всё же хочется изменить, решение очевидно – поместить её в кучу. Для этого нужно использовать любого рода обертку: одноэлементный массив, объект-atomic, специально созданный класс с этой переменной как полем.
Хак с оберткой решает проблему времени жизни и даёт коду скомпилироваться, но возвращает проблему сложности поведения. Если среда многопоточная, то вероятно порядок операций с этой переменной придется синхронизировать вручную.
#Язык #Многопоточность
👍6
Forwarded from Книги по Java | Books Java 📚
Spring в действии 6 изд
Крейг Уоллс (2022)
Фреймворк Spring Framework - необходимый инструмент для разработчиков приложений на Java. В книге описана последняя версия Spring 3, который несет в себе новые мощные особенности, такие как язык выражений SpEL, новые аннотации для работы с контейнером IоС и поддержка архитектуры REST. Крейг Уоллс обладает особым талантом придумывать весьма интересные примеры, сосредоточенные на особенностях и приемах использования Spring, которые действительно будут полезны читателям.
Издание предназначено как для начинающих пользователей фреймворка, так и для опытных пользователей Spring, желающих задействовать новые возможности версии 3.0.
Скачать
👉 @java_360
Крейг Уоллс (2022)
Фреймворк Spring Framework - необходимый инструмент для разработчиков приложений на Java. В книге описана последняя версия Spring 3, который несет в себе новые мощные особенности, такие как язык выражений SpEL, новые аннотации для работы с контейнером IоС и поддержка архитектуры REST. Крейг Уоллс обладает особым талантом придумывать весьма интересные примеры, сосредоточенные на особенностях и приемах использования Spring, которые действительно будут полезны читателям.
Издание предназначено как для начинающих пользователей фреймворка, так и для опытных пользователей Spring, желающих задействовать новые возможности версии 3.0.
Скачать
👉 @java_360
👍9😁3❤1
Как получить гарантированный дедлок?
Сначала поговорим о том, что это такое. Deadlock – это взаимная блокировка, ситуация, когда два или более потока «наступают друг-другу на хвост» – зависают в вечном ожидании ресурсов, захваченных друг другом.
Livelock – похожая проблема, с тем лишь отличием, что потоки не останавливаются, а вместо этого зацикливаются, выполняя одни и те же бесполезные действия, ходят по кругу.
Стандартный подход к обеспечению гарантии защиты от дедлока – установка строгого порядка взятия блокировок. Если для мониторов A и B соблюдается всеобщий порядок захвата AB (и соответственно отпускания BA), то ни с одним потоком не случится попасть на ожидание B, успешно при этом захватив A.
Из этого можно догадаться, простой способ гарантировать возможность дедлока – явно нарушить это условие.
Нарушение условия даст дедлок «скорее всего когда-нибудь». Чтобы получить его точно и с первого раза, нужно гарантировать, что оба потока окажутся на этапе между захватами одного и другого ресурса в одно время. Это можно сделать множеством способов, в примере ниже использован
Вопрос дедлоков – одна из краеугольных тем параллельных вычислений, уходящая далеко за рамки этого вопроса. Для дальнейшего изучения рекомендуются статьи на википедии про дедлоки, про задачу об обедающих философах как классическая иллюстрация проблемы, и глава 10.1 Java Concurrency in Practice.
#Многопоточность
Сначала поговорим о том, что это такое. Deadlock – это взаимная блокировка, ситуация, когда два или более потока «наступают друг-другу на хвост» – зависают в вечном ожидании ресурсов, захваченных друг другом.
Livelock – похожая проблема, с тем лишь отличием, что потоки не останавливаются, а вместо этого зацикливаются, выполняя одни и те же бесполезные действия, ходят по кругу.
Стандартный подход к обеспечению гарантии защиты от дедлока – установка строгого порядка взятия блокировок. Если для мониторов A и B соблюдается всеобщий порядок захвата AB (и соответственно отпускания BA), то ни с одним потоком не случится попасть на ожидание B, успешно при этом захватив A.
Из этого можно догадаться, простой способ гарантировать возможность дедлока – явно нарушить это условие.
Нарушение условия даст дедлок «скорее всего когда-нибудь». Чтобы получить его точно и с первого раза, нужно гарантировать, что оба потока окажутся на этапе между захватами одного и другого ресурса в одно время. Это можно сделать множеством способов, в примере ниже использован
CyclicBarrier
.Вопрос дедлоков – одна из краеугольных тем параллельных вычислений, уходящая далеко за рамки этого вопроса. Для дальнейшего изучения рекомендуются статьи на википедии про дедлоки, про задачу об обедающих философах как классическая иллюстрация проблемы, и глава 10.1 Java Concurrency in Practice.
#Многопоточность
👍3
В чём отличия интерфейса от абстрактного класса?
Главное отличие – это семантика. Интерфейсы появились еще до Java, как важная концепция ООП. Смысл интерфейса – некое поведение, описание свойства. Причем если придерживаться принципа сегрегации интерфейсов, это описание единственного аспекта поведения.
Класс, даже абстрактный – это комбинация всех свойств и их реализаций, которыми определяются сущности некоторой категории (собственно, класса).
Отсюда вытекает естественность и необходимость множественного наследования для интерфейсов. Опыт таких языков как C++ показал, что множественное наследование классов не нужно и проблемно (см. проблема ромбовидного наследования). По факту же обычно нужно всего лишь переиспользование кода, что не относится к ООП и реализуется в некоторых языках «интерфейсами с независимым состоянием» – примесями.
В Java интерфейс в отличие от абстрактного класса не может иметь состояния. Реализация поведения же допустима только в двух случаях: для статических методов, и
В интерфейсах, как публичных описаниях, не имеют смысла и запрещены непубличные члены. Отсюда синтаксическое отличие: модификатор
На уровне скомпилированного байткода тоже есть небольшие различия: интерфейс помечается флагом
И есть еще одно небольшое отличие. Интерфейс с одним методом можно использовать как функциональный, и инстанциировать лямбда-выражением. Для абстрактного класса даже с единственным методом такое не сработает.
#Классы
Главное отличие – это семантика. Интерфейсы появились еще до Java, как важная концепция ООП. Смысл интерфейса – некое поведение, описание свойства. Причем если придерживаться принципа сегрегации интерфейсов, это описание единственного аспекта поведения.
Класс, даже абстрактный – это комбинация всех свойств и их реализаций, которыми определяются сущности некоторой категории (собственно, класса).
Отсюда вытекает естественность и необходимость множественного наследования для интерфейсов. Опыт таких языков как C++ показал, что множественное наследование классов не нужно и проблемно (см. проблема ромбовидного наследования). По факту же обычно нужно всего лишь переиспользование кода, что не относится к ООП и реализуется в некоторых языках «интерфейсами с независимым состоянием» – примесями.
В Java интерфейс в отличие от абстрактного класса не может иметь состояния. Реализация поведения же допустима только в двух случаях: для статических методов, и
default
для обычных. Статические методы являются частью всего класса, а не экземпляров. Дефолтная реализация, как говорилось ранее, добавлена только как хак для сохранения совместимости.В интерфейсах, как публичных описаниях, не имеют смысла и запрещены непубличные члены. Отсюда синтаксическое отличие: модификатор
public
, как и abstract
для методов или static
для полей, можно не писать. Запрещены и модификаторы, несовместимые с abstract
: final
, synchronized
и прочие.На уровне скомпилированного байткода тоже есть небольшие различия: интерфейс помечается флагом
ACC_INTERFACE
а для класса генерируется конструктор по-умолчанию.И есть еще одно небольшое отличие. Интерфейс с одним методом можно использовать как функциональный, и инстанциировать лямбда-выражением. Для абстрактного класса даже с единственным методом такое не сработает.
#Классы
👍11
Конференция JPoint 2022 на день вернется в офлайн 24 июня.
Для всех, кто соскучился по живому общению с единомышленниками, команда JPoint сделала отдельный офлайн-день.
В теплом ламповом офлайне вас ждут:
✔ Доклады о фреймворках, тестировании на Java, архитектуре, многопоточности и не только.
✔ Дискуссии с участием спикеров после каждого доклада (вживую, а не в Zoom!).
✔ Круглые столы о JVM, Data Engineering в Java и собеседованиях разработчиков.
✔ Партнерские стенды с мини-докладами и возможностью получить мерч.
✔ Живое общение с единомышленниками.
📍Санкт-Петербург, Park Inn by Radisson Pulkovskaya, 24 июня
Вместе с билетом на офлайн-день вы получите доступ к записям более чем 30 докладов и других активностей с онлайн-части конференции.
Подробности, полную программу и билеты вы можете найти по ссылке.
Если покупаете билет за свой счет (а не за счет компании), то воспользуйтесь промокодом для получения скидки:
Для всех, кто соскучился по живому общению с единомышленниками, команда JPoint сделала отдельный офлайн-день.
В теплом ламповом офлайне вас ждут:
✔ Доклады о фреймворках, тестировании на Java, архитектуре, многопоточности и не только.
✔ Дискуссии с участием спикеров после каждого доклада (вживую, а не в Zoom!).
✔ Круглые столы о JVM, Data Engineering в Java и собеседованиях разработчиков.
✔ Партнерские стенды с мини-докладами и возможностью получить мерч.
✔ Живое общение с единомышленниками.
📍Санкт-Петербург, Park Inn by Radisson Pulkovskaya, 24 июня
Вместе с билетом на офлайн-день вы получите доступ к записям более чем 30 докладов и других активностей с онлайн-части конференции.
Подробности, полную программу и билеты вы можете найти по ссылке.
Если покупаете билет за свой счет (а не за счет компании), то воспользуйтесь промокодом для получения скидки:
bookjava2022JRGpc
👍4
Что если оба реализуемых интерфейса объявляют один и тот же метод? (1/2)
Если объявление полностью одинаково – нет никакой проблемы, класс-реализация должен просто определить этот метод.
Когда у обоих интерфейсов объявлены методы с одинаковой сигнатурой, но разными возвращаемыми типами – всё зависит от того, какие именно эти типы.
Переопределение метода (override) еще с Java 5 ковариантно относительно возвращаемого типа. То есть, в наследнике тип результата метода может быть наследником:
Если типы не связаны отношением наследования, например
Для примитивов никакой ковариантности возвращаемого типа нет. Даже если типы совместимы относительно присваивания:
Если различие в части throws, методы объявлены выбрасывающими разные типы исключений. Правила здесь те же, что для возвращаемых типов – работает ковариантность. Отличие лишь в том, что исключений примитивных типов не бывает, а даже для не являющихся родителем и наследником исключений всегда есть вариант, удовлетворяющий обоим – отсутствие выбрасываемых исключений вообще.
#Классы
Если объявление полностью одинаково – нет никакой проблемы, класс-реализация должен просто определить этот метод.
Когда у обоих интерфейсов объявлены методы с одинаковой сигнатурой, но разными возвращаемыми типами – всё зависит от того, какие именно эти типы.
Переопределение метода (override) еще с Java 5 ковариантно относительно возвращаемого типа. То есть, в наследнике тип результата метода может быть наследником:
super
метод возвращает Number
, @Override
метод возвращает Integer
.Если типы не связаны отношением наследования, например
String
и Long
– такой класс невозможно реализовать.Для примитивов никакой ковариантности возвращаемого типа нет. Даже если типы совместимы относительно присваивания:
int
→long
, int
→Integer
. В любом из таких случаев будет ошибка о несовместимости возвращаемых типов, для примитивов они должны совпадать в точности. Если различие в части throws, методы объявлены выбрасывающими разные типы исключений. Правила здесь те же, что для возвращаемых типов – работает ковариантность. Отличие лишь в том, что исключений примитивных типов не бывает, а даже для не являющихся родителем и наследником исключений всегда есть вариант, удовлетворяющий обоим – отсутствие выбрасываемых исключений вообще.
#Классы
👍5🔥1
Что если оба реализуемых интерфейса объявляют один и тот же метод? (2/2)
Если метод дефолтный в обоих интерфейсах, то есть снабжен реализацией. Компилятору изначально понятно, что невозможно будет на момент вызова определить, какой метод реально подразумевался. Так что еще на объявлении класса-реализации компилятор выдаст ошибку «inherits unrelated defaults».
В таком случае в классе необходимо переопределить этот общий метод, и явно перенаправить вызов в нужную родительскую реализацию. Для этого есть синтаксис явного указания нужного базового интерфейса
Ошибка появится даже в случае, когда реализации полностью совпадают. Если бы компилятор такое позволял, нельзя было бы безопасно изменить и перекомпилировать только одну из реализаций.
На практике с этой ситуацией можно столкнуться даже с классами стандартной библиотеки. Например, реализуя
Если в хоть одном из интерфейсов-предков метод не имеет дефолтной реализации, ошибка компиляции будет предлагать альтернативное решение – сделать класс абстрактным. Добавление ключевого слова
Других различий не бывает – изменение других модификаторов метода интерфейса недопустимо, а изменение списка параметров и имени делает его просто другим методом.
#Классы
Если метод дефолтный в обоих интерфейсах, то есть снабжен реализацией. Компилятору изначально понятно, что невозможно будет на момент вызова определить, какой метод реально подразумевался. Так что еще на объявлении класса-реализации компилятор выдаст ошибку «inherits unrelated defaults».
В таком случае в классе необходимо переопределить этот общий метод, и явно перенаправить вызов в нужную родительскую реализацию. Для этого есть синтаксис явного указания нужного базового интерфейса
MyBase.super.doSmth()
. См. пример ниже.Ошибка появится даже в случае, когда реализации полностью совпадают. Если бы компилятор такое позволял, нельзя было бы безопасно изменить и перекомпилировать только одну из реализаций.
На практике с этой ситуацией можно столкнуться даже с классами стандартной библиотеки. Например, реализуя
List
и Set
одним классом, вы получите эту ошибку для метода spliterator()
.Если в хоть одном из интерфейсов-предков метод не имеет дефолтной реализации, ошибка компиляции будет предлагать альтернативное решение – сделать класс абстрактным. Добавление ключевого слова
abstract
вернет вас в ту же неоднозначную ситуацию, и на ту же первоначальную ошибку.Других различий не бывает – изменение других модификаторов метода интерфейса недопустимо, а изменение списка параметров и имени делает его просто другим методом.
#Классы
👍2
Начните изучать Java бесплатно с нашим курсом «Основы языка Java».
Курс ориентирован на студентов, не знакомых с профессией и желающих понять его основы. Вы сможете рассмотреть такие необходимые понятия, как работа с ошибками, отладка, использование основных конструкций языка.
Всего 29 уроков, 105 проверочных тестов и 34 упражнения в тренажере.
Переходите прямо сейчас: https://clck.ru/rFRCL
Курс ориентирован на студентов, не знакомых с профессией и желающих понять его основы. Вы сможете рассмотреть такие необходимые понятия, как работа с ошибками, отладка, использование основных конструкций языка.
Всего 29 уроков, 105 проверочных тестов и 34 упражнения в тренажере.
Переходите прямо сейчас: https://clck.ru/rFRCL
50+ задач по структуре данных и алгоритмам
https://dev.to/javinpaul/50-data-structure-and-algorithms-problems-from-coding-interviews-4lh2
#java #programming
https://dev.to/javinpaul/50-data-structure-and-algorithms-problems-from-coding-interviews-4lh2
#java #programming
👍5
Как устроены атомики?
Начнем с того, что такое атомики и зачем нужны.
Условно можно разделить подходы реализации большинства atomic-методов на две группы: compare-and-set и set-and-get.
Методы категории compare-and-set принимают старое значение и новое. Если переданное старое значение совпало с текущим, устанавливается новое. Обычно делегируют вызов в методы класса
В случае set-and-get старое значение неизвестно. Поэтому нужен небольшой трюк: программа сначала считывает текущее значение, а затем записывает новое, тоже с помощью CAS, потому что запись могла успеть поменяться даже за этот шаг. Эта попытка чтения+записи повторяется в цикле, пока старое значение не совпадет и переменная не будет успешно записана.
Этот трюк называется double-checked или optimistic locking, и может быть использован и в пользовательском коде с любым способом синхронизации. Оптимистичность заключается в том, что мы надеемся что состояния гонки нет, прибегая к синхронизации только если гонка всё же случилась. Реализация оптимистичной блокировки может быть дана как отдельная задача.
#Многопоточность
Начнем с того, что такое атомики и зачем нужны.
Atomic*
– семейство классов из java.util.concurrent
. Они предоставляют набор атомарных операций для соответствующих типов. Например с помощью методов getAndIncrement
/incrementAndGet
класса AtomicInteger
можно делать неатомарный в обычных условиях инкремент (i++
).Условно можно разделить подходы реализации большинства atomic-методов на две группы: compare-and-set и set-and-get.
Методы категории compare-and-set принимают старое значение и новое. Если переданное старое значение совпало с текущим, устанавливается новое. Обычно делегируют вызов в методы класса
Unsafe
, которые заменяются нативными реализациями виртуальной машины. Виртуальная машина в большинстве случаев использует атомарную операцию процессора compare-and-swap (CAS). Поэтому атомики обычно более эффективны чем стандартная дорогостоящая блокировка.В случае set-and-get старое значение неизвестно. Поэтому нужен небольшой трюк: программа сначала считывает текущее значение, а затем записывает новое, тоже с помощью CAS, потому что запись могла успеть поменяться даже за этот шаг. Эта попытка чтения+записи повторяется в цикле, пока старое значение не совпадет и переменная не будет успешно записана.
Этот трюк называется double-checked или optimistic locking, и может быть использован и в пользовательском коде с любым способом синхронизации. Оптимистичность заключается в том, что мы надеемся что состояния гонки нет, прибегая к синхронизации только если гонка всё же случилась. Реализация оптимистичной блокировки может быть дана как отдельная задача.
#Многопоточность
👍6👏1
Подборка каналов для IT специалистов 🎯
https://t.me/odin1C_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://t.me/kotlin_lib Подборки полезного материала по Kotlin
https://t.me/nodejs_lib Подборки по Node js и все что с ним связано
https://t.me/React_lib Подборки по React js и все что с ним связано
Программирование C++📌
https://t.me/cpp_lib Библиотека C/C++ разработчика
https://t.me/cpp_knigi Книги для программистов C/C++
Программирование Python 📌
https://t.me/pythonofff Python академия. Учи Python быстро и легко🐍
https://t.me/BookPython Библиотека Python разработчика
https://t.me/python_real Python подборки на русском и английском
https://t.me/python_360 Книги по Python Rus
Java разработка 📌
https://t.me/BookJava Библиотека Java разработчика
https://t.me/java_360 Книги по Java Rus
GitHub Сообщество 📌
https://t.me/Githublib Интересное из GitHub
CodePen 📌
https://t.me/codepen_1 Сообщество пользователей CodePen
Базы данных (Data Base) 📌
https://t.me/database_info Все про базы данных
Мобильная разработка: iOS, Android 📌
https://t.me/developer_mobila Мобильная разработка
Фронтенд разработка 📌
https://t.me/frontend_1 Подборки для frontend разработчиков
Разработка игр 📌
https://t.me/game_devv Все о разработке игр
Вакансии 📌
https://t.me/sysadmin_rabota Системный Администратор
https://t.me/progjob Вакансии в IT
Чат программистов📌
https://t.me/developers_ru
Библиотеки 📌
https://t.me/book_for_dev Книги для программистов Rus
https://t.me/programmist_of Книги по программированию
https://t.me/proglb Библиотека программиста
https://t.me/bfbook Книги для программистов
БигДата, машинное обучение 📌
https://t.me/bigdata_1 Data Science, Big Data, Machine Learning, Deep Learning
Программирование 📌
https://t.me/bookflow Лекции, видеоуроки, доклады с IT конференций
https://t.me/coddy_academy Полезные советы по программированию
QA, тестирование 📌
https://t.me/testlab_qa Библиотека тестировщика
Шутки программистов 📌
https://t.me/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://t.me/thehaking Канал о кибербезопасности
Книги, статьи для дизайнеров 📌
https://t.me/ux_web Статьи, книги для дизайнеров
https://t.me/arhitekturamira World Architecture
Системное администрирование 📌
https://t.me/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
https://t.me/sysadminof Книги для админов, полезные материалы
https://t.me/i_odmin Все для системного администратора
https://t.me/i_odmin_book Библиотека Системного Администратора
https://t.me/i_odmin_chat Чат системных администраторов
https://t.me/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
Английский 📌
https://t.me/UchuEnglish Английский с нуля
Математика 📌
https://t.me/Pomatematike Канал по математике
Арбитраж трафика 📌
https://t.me/partnerochkin CPA и арбитраж трафика
Крипта 📌
https://t.me/bitkoinoff Новости криптовалют
Метавселенная, GameFi, Crypto 📌
https://t.me/metaverse360
DeepFake 📌
https://t.me/deepfakenow Публикуем deepfake видео
Мир технологий 📌
https://t.me/mir_teh Видео из мира технологий
Excel лайфхак📌
https://t.me/Excel_lifehack
https://t.me/odin1C_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://t.me/kotlin_lib Подборки полезного материала по Kotlin
https://t.me/nodejs_lib Подборки по Node js и все что с ним связано
https://t.me/React_lib Подборки по React js и все что с ним связано
Программирование C++📌
https://t.me/cpp_lib Библиотека C/C++ разработчика
https://t.me/cpp_knigi Книги для программистов C/C++
Программирование Python 📌
https://t.me/pythonofff Python академия. Учи Python быстро и легко🐍
https://t.me/BookPython Библиотека Python разработчика
https://t.me/python_real Python подборки на русском и английском
https://t.me/python_360 Книги по Python Rus
Java разработка 📌
https://t.me/BookJava Библиотека Java разработчика
https://t.me/java_360 Книги по Java Rus
GitHub Сообщество 📌
https://t.me/Githublib Интересное из GitHub
CodePen 📌
https://t.me/codepen_1 Сообщество пользователей CodePen
Базы данных (Data Base) 📌
https://t.me/database_info Все про базы данных
Мобильная разработка: iOS, Android 📌
https://t.me/developer_mobila Мобильная разработка
Фронтенд разработка 📌
https://t.me/frontend_1 Подборки для frontend разработчиков
Разработка игр 📌
https://t.me/game_devv Все о разработке игр
Вакансии 📌
https://t.me/sysadmin_rabota Системный Администратор
https://t.me/progjob Вакансии в IT
Чат программистов📌
https://t.me/developers_ru
Библиотеки 📌
https://t.me/book_for_dev Книги для программистов Rus
https://t.me/programmist_of Книги по программированию
https://t.me/proglb Библиотека программиста
https://t.me/bfbook Книги для программистов
БигДата, машинное обучение 📌
https://t.me/bigdata_1 Data Science, Big Data, Machine Learning, Deep Learning
Программирование 📌
https://t.me/bookflow Лекции, видеоуроки, доклады с IT конференций
https://t.me/coddy_academy Полезные советы по программированию
QA, тестирование 📌
https://t.me/testlab_qa Библиотека тестировщика
Шутки программистов 📌
https://t.me/itumor Шутки программистов
Защита, взлом, безопасность 📌
https://t.me/thehaking Канал о кибербезопасности
Книги, статьи для дизайнеров 📌
https://t.me/ux_web Статьи, книги для дизайнеров
https://t.me/arhitekturamira World Architecture
Системное администрирование 📌
https://t.me/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
https://t.me/sysadminof Книги для админов, полезные материалы
https://t.me/i_odmin Все для системного администратора
https://t.me/i_odmin_book Библиотека Системного Администратора
https://t.me/i_odmin_chat Чат системных администраторов
https://t.me/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
Английский 📌
https://t.me/UchuEnglish Английский с нуля
Математика 📌
https://t.me/Pomatematike Канал по математике
Арбитраж трафика 📌
https://t.me/partnerochkin CPA и арбитраж трафика
Крипта 📌
https://t.me/bitkoinoff Новости криптовалют
Метавселенная, GameFi, Crypto 📌
https://t.me/metaverse360
DeepFake 📌
https://t.me/deepfakenow Публикуем deepfake видео
Мир технологий 📌
https://t.me/mir_teh Видео из мира технологий
Excel лайфхак📌
https://t.me/Excel_lifehack
👍3