Поиск сообщества

Показаны результаты для тегов 'java'.

  • Поиск по тегам

    Введите теги через запятую.
  • Поиск по автору

Тип контента


Форумы

  • METORY.RU
    • Общий
    • доска обьявлений
  • Общий раздел
    • Промокоды
    • Аккаунты соц сетей
    • Компьютеры
    • web разработка
    • Програмирование
    • Сайты, скрипты, домены
    • Графика и дизайн
  • Технический раздел
    • Статьи
    • Вопрос-ответ
    • Необходимые действия для работы читов
  • Игротека
    • ARK SURVIVAL EVOLVED
    • Apex Legend
    • PUBG
    • PUBG LITE
    • PUBG MOBILE
    • RUST
    • WARZONE
    • Калибр
    • Warface
    • Escape from Tarkov
    • Tom Clancy's Rainbow Six® Siege
    • Fortnite
    • Battlefield V
    • GTA 5
    • Cs go
    • DOTA 2
    • World of Tanks
    • Сталкер 2
    • Overwatch
    • Minecraft
    • Xbox
    • cs 1.6
    • Браузерные игры
  • Лаборатория Хакера

Блоги

Без результатов

Без результатов

Группы продуктов

  • Вип продукция
  • VIP 1lvl
  • приватный чит тест

Категории


Поиск результатов в...

Поиск контента, содержащего...


Дата создания

  • Начало

    Конец


Дата обновления

  • Начало

    Конец


Фильтр по количеству...

Регистрация

  • Начало

    Конец


Группа


Обо мне

Найдено: 13 результатов

  1. 5 особенностей языка Java, о которых вы должны знать О некоторых особенностях языка Java порой не знают сами джависты. Рассказываем о 5 особенностях этого языка, которые должен знать каждый. Инструкции можно переставлять Рассмотрим пример: int x = 1; int y = 3; System.out.println(x + y); Взглянув на код, мы можем предположить, что сначала значение 1 будет присвоено x, а затем значение 3 – переменной y. Но если порядок присвоения переменных изменится, конечный результат окажется тем же: мы увидим в выводе 4. Используя это наблюдение, компилятор может безопасно изменить порядок этих двух операций присваивания в случае необходимости. Когда мы компилируем код, компилятор делает именно это: он может свободно переупорядочивать инструкции, если это не изменяет ожидаемое поведение системы. И хотя перестановка в примере выше не приведет ни к каким положительным изменениям, есть ситуации, когда изменение порядка инструкций повысит производительность. Предположим, в нашем коде переменные x и y дважды увеличиваются в процессе чередования. int x = 1; int y = 3; x++; y++; x++; y++; System.out.println(x + y); Этот код должен вывести 8, и он сделает это даже при изменении порядка операций. В процессе оптимизации компилятор может полностью избавить код от операций приращения: // Смена порядка инструкций int x = 1; int y = 3; x++; x++; y++; y++; System.out.println(x + y); // Сжатие инструкций int x = 1; int y = 3; x += 2; y += 2; System.out.println(x + y); // Полное сжатие инструкций int x = 3; int y = 5; System.out.println(x + y); В действительности, компилятор продвинется еще на шаг дальше и переместит значения x и y сразу в print. В числовых значениях можно использовать нижние подчеркивания Читать большие числа неудобно. Для удобства чтения в математике их принято разделять точкой: вместо 1183548876845 получается вполне читабельное 1.183.548.876.845. К сожалению, в коде языка Java часто встречаются огромные числа в виде констант, как в примере ниже. public class Foo { public static final long LARGE_FOO = 1183548876845l; } System.out.println(LARGE_FOO); Конечно, содержимое констант редко претендует на эстетичность, но разбирать такое число глазами не очень удобно. К счастью, в Java Development Kit (JDK) седьмой версии появилась возможность разделять числа с помощью нижнего подчеркивания ( _ ) так же, как мы привыкли делать это с помощью точки. public class Foo { public static final long LARGE_FOO = 1_183_548_876_845l; } System.out.println(LARGE_FOO); Таким же образом, кстати, можно разделять и длинные хвосты десятичных дробей. public static final double LARGE_BAR = 1.546_674_876; Автоупаковка целочисленных кэшируется В Java мы можем записать целочисленное значение следующим образом: Integer myInt = 500; Примитив int 500 преобразуется в объект типа Integer и сохраняется в myInt. Такая обработка называется автобоксинг или автоупаковка, поскольку преобразование примитива в объект типа Integer происходит автоматически. Так как myInt является объектом типа Integer, мы ожидаем, что сравнение его с другим объектом того же типа и содержащего то же значение с помощью оператора == приведет к false. Но вызов сравнения для этих двух объектов приведет к true, так как оба объекта представлены одним значением – 500: Integer myInt = 500; Integer otherInt = 500; System.out.println(myInt == otherInt); // false System.out.println(myInt.equals(otherInt)); // true На этом этапе autoboxing работает точно так, как ожидается, но что произойдет, если мы попробуем это с меньшим числом? Например, 25: Integer myInt = 25; Integer otherInt = 25; System.out.println(myInt == otherInt); // true System.out.println(myInt.equals(otherInt)); // true Тут мы увидим, что оба объекта равны как по ==, так и по equals. Это означает, что два объекта Integer, фактически являются одним объектом. Такое странное поведение, на самом деле, не является ни ошибкой, ни недосмотром: оно было допущено умышленно. Поскольку многие операции автобоксинга выполняются с небольшими числами (до 127), JLS говорит о том, что значения Integer в диапазоне от -128 до 127 включительно кэшируются. Файлы языка Java могут содержать множество не вложенных классов В одном файле исходного кода может разместиться множество не вложенных классов, которые не являются public. Для них уровень доступа будет установлен как package-private (то есть, без модификатора доступа). При этом, в каждом файле может содержаться только один public класс. public class Foo { //... } class Bar { //... } class Bar1 { //... } После компиляции количество файлов будет равно количеству не вложенных классов. StringBuilder используется для конкатенации строк Конкатенация строк присутствует почти во всех языках программирования и позволяет соединять между собой строки или объекты и примитивы разных типов в одну строку. Одна из сложностей конкатенации в Java – неизменяемость строк. То есть мы не можем все время добавлять данные к одной и той же строке. Каждый append будет создавать новый объект String и продолжать работу с ним. Это не критично, когда нужно произвести конкатенацию нескольких строк, но когда объемы вырастают до, скажем, 1000 строк, эта техника становится нестабильной, да и создавать тысячу сущностей String довольно расточительно. Однако документация JLS сообщает, что подобного можно избежать. Для этого нужно использовать StringBuilder, который поработает как буфер. Строка, к которой идет присоединение других строк, будет существовать до тех пор, пока она нужна. Таким образом, при конкатенации будет существовать всего одна сущность String. Для примера, соединение строк с помощью StringBuilder в цикле выглядит так: StringBuilder builder = new StringBuilder(); for (int i = 0; i < 1000; i++) { builder.append("a"); } String myString = builder.toString();
  2. Разбираемся с тем, как устроены функции в Java и Kotlin Изучаете Java и Kotlin? Тогда вам будет полезно узнать о различных типах синхронизации, блокировки и обеспечения безопасности потоков. Что такое синхронизация? В многопоточном мире нам необходим доступ к объектам между потоками, при отсутствии синхронизации могут возникнуть проблемы. Вот базовый пример, который объясняет, почему это нужно: import kotlinx.coroutines.* fun main() = runBlocking { var sharedCounter = 0 val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for(i in 1..1000){ sharedCounter++ } } } coroutines.forEach { corotuine-> corotuine.join() } }.join() println("The number of shared counter should be 10000000, but actually is $sharedCounter") } Здесь мы запускаем 1000 корутинов (можете считать их легковесными потоками) в 4 потоках, и каждый из них увеличивает величину sharedCounter 1 000 раз, так, чтобы в итоге оно стало равным 1 000 000. Шансов, что у вас получится это сделать, практически нет. Прежде чем мы объясним происходящее, представьте, что есть одна комната для размышлений (counter) и очередь из людей (threads), где каждый хочет зайти внутрь, при этом, она рассчитана только на одного человека. В этой комнате есть дверь, которую закрывают, если кто-то уже внутри. Но есть проблема − прямо сейчас эту дверь можно открыть снаружи, потому что в ней нет замка. В приведенном выше коде для увеличения значения sharedCounter каждый поток пытается сделать следующее: Получить текущее значение. Сохранить его во временной переменной и увеличьте временную переменную на 1. Сохранить значение временной переменной в sharedCounter. Но что если пока один поток получает текущее значение, подключится другой и тоже попытается получить текущее значение? Они оба получат одно и то же значение. Таким образом, каждый из них увеличивает это значение на 1 и сохраняет его. Эта проблема может возникать и в других ситуациях: например, когда поток проходит второй шаг, другие потоки увеличивают и сохраняют значение sharedCounter, а при переходе на следующий шаг, первый поток сохраняет неактуальную версию. Тогда итоговое значение будет не таким, каким должно. Чтобы решить эту проблему, нужно синхронизировать потоки для работы над этим значением. Далее рассказываем, как это сделать. Volatile В Java и Kotlin есть ключевое слово volatile (в Kotline в виде аннотации @volatile), которые применяются к полям и гарантируют, что считываемое значение поступает из основной памяти, а не из кэша процессора, поэтому все участники процесса будут ожидать окончания параллельной записи, прежде чем считать значение. import kotlinx.coroutines.* @Volatile var sharedCounter: Int = 0 fun main() = runBlocking { val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { sharedCounter++ } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is $sharedCounter") } Как видно из кода, в нашем сценарии volatiles не помогают, потому что volatile не спасает от проблемы считывания устаревшего значения (между чтением переменной и записью нового значения в одном потоке может произойти действие в другом). Дело в том, что ++ здесь − не атомарная операция. Volatiles используются для создания потоко-безопасных синглетонов в Java путем двойной проверки экземпляра синглтона. Подробнее об этих методах написано здесь. Synchronized Одним из решений является использование synchronized из Java. В Java и Kotlin есть два типа синхронизации − синхронизированные методы и синхронизированные блоки. Для понимания − синхронизированные методы обозначается, как synchronized в Java и @Synchronized в Kotlin. Чтобы использовать это решение, мы можем перенести увеличение счётчика в отдельную функцию, помеченную как synchronized. import kotlinx.coroutines.* var sharedCounter = 0 @Synchronized fun updateCounter(){ sharedCounter++ } fun main() = runBlocking { val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for(i in 1..1000){ updateCounter() } } } coroutines.forEach { corotuine-> corotuine.join() } }.join() println("The number of shared counter is $sharedCounter") } Как видим, выходное значение верное. Если провести аналогию с комнатами, то @Synchronizedбудет являться замком на двери, который открывается единственным существующим ключом. Один человек (поток) берёт ключ, заходит, закрывается изнутри. Никто другой не может зайти в этот момент. Только после того, как ключ вернут на место, это станет возможным. Именно так работает synchronized в Java и Kotlin. Следует отметить ещё один момент, который касается синхронизированных методов: доступ ко всему методу ограничен, даже если происходят промежуточные действия, не требующие синхронизации. Например, при использовании synchronized, updateCounter синхронизируется даже тогда, когда sharedCounter не обновляется. Синхронизация может происходить и на более низком уровне, например, с помощью синхронизированных блоков. Давайте попробуем увеличивать число только тогда, когда наш номер итерации четный. import kotlinx.coroutines.* class Incrementor() { var sharedCounter: Int = 0 private set fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { synchronized(this) { sharedCounter++ } } } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.sharedCounter}") } Синхронизированные блоки хороши тем, что позволяют использовать любой объект в качестве замка. В приведенном выше коде, synchronized определяет this (экземпляр Instance) как объект блокировки. Любой поток, который достигает этой точки, блокирует Incrementor, выполняет работу в блоке и снимает блокировку synchronized. Атомарные примитивы Атомарные примитивы позволяют синхронизировать действия над переменными базовых типов. Например, в примере ниже класс AtomicIntegerпредоставляет свою реализацию функции ++. import kotlinx.coroutines.* import java.util.concurrent.atomic.AtomicInteger class Incrementor() { val sharedCounter: AtomicInteger = AtomicInteger(0) fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { sharedCounter.incrementAndGet() } } fun getSharedCounter():Int { return sharedCounter.get() } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.getSharedCounter()}") } Lock Класс Lock выполняет примерно те же функции, что и synchronized, только более гибко. synchronizedдаёт возможность синхронизировать блоки кода, тогда как с Lock можно реализовывать более сложную логику. Примерно так: import kotlinx.coroutines.* import java.util.concurrent.locks.ReentrantLock class Incrementor() { private val sharedCounterLock = ReentrantLock() var sharedCounter: Int = 0 private set fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { try { sharedCounterLock.lock() sharedCounter++ } finally { sharedCounterLock.unlock() } } } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.sharedCounter}") } Благодаря «замкам» можно, например, организовать последовательную блокировку-разблокировку объектов LinkedList или ввести иерархическую блокировку. Взаимные блокировки − большая область для отдельного изучения. Кроме того, классы Lock предоставляют удобные функции, например Lock.tryLock(long, TimeUnit), которая попытается получить блокировку над объектом, но не будет ожидать вечность. Средства параллелизма Существуют также и другие классы для обеспечения параллельности и синхронизации. Рассмотрим на примерах. 1. Semaphore Semaphore, как и замки, могут быть использованы для синхронизации. Lock (можно рассматривать его как мьютекс) используется, когда нужно что-то сделать линейным и атомарным способом. Semaphore принимают общее количество разрешений в своем конструкторе, которое может использоваться для ограничения одновременного доступа к ресурсу. Они используются тогда, когда требуются сигналы. Существует некоторый паттерн: producer − consumer. Одни потоки ведут запись и подают об этом сигнал. Другие следят за состоянием сигналов, только чтобы осуществлять чтение данных. Посмотрите на пример кода ниже: сейчас мы адаптируем его так, чтобы можно было использовать semaphore. Хотя в данном случае можно найти и более удачные способы обеспечения синхронизации. import kotlinx.coroutines.* import java.util.concurrent.Semaphore class Incrementor() { private val sharedCounterLock = Semaphore(1) var sharedCounter: Int = 0 private set fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { try { sharedCounterLock.acquire() sharedCounter++ } finally { sharedCounterLock.release() } } } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.sharedCounter}") } Ещё, функция semaphore обеспечивает справедливую очередность исполнения потоков, если задать второй параметр в конструкторе − fair. Тогда поток, ожидающий очереди дольше всего, получит доступ первым. То есть, поддерживается FIFO очередь. Однако использование tryAcquireнапрямую не сохраняет эту «честность». Если требуется соблюдение правила FIFO, можно писать tryAcquire(0, TimeUnit.SECONDS). 2. CyclicBarrier CyclicBarrier используется, когда есть фиксированное количество участников − потоков или методов, ожидающих окончания выполнения процесса каждым из участников для продолжения работы. Представим, что у нас есть несколько исполнителей, генерирующих по одному числу, и мы хотим суммировать все числа после окончания работы. import java.util.* import java.util.concurrent.CyclicBarrier fun main() { val createdValues = mutableListOf<Int>() /** * create a cyclic barrier that waits for 5 threads to finish their jobs, and after that, * prints the sum of all the values. */ val cyclicBarrier = CyclicBarrier(3) { println("Sum of all values is ${createdValues.sum()}") } val threads = 1.rangeTo(3).map { number -> Thread { Thread.sleep(Random().nextInt(500).toLong()) createdValues.add(number) cyclicBarrier.await() println("I am thread ${Thread.currentThread().name} and I finished my Job!") }.apply { start() } } threads.forEach { thread -> thread.join() } } В приведенном примере, блок кода, который был передан конструктору CyclicBarrier, запускается после выполнения 3 потоками await(). После выполнения действия CyclicBarrier потоки продолжают работать с тех мест, где остановились. Еще один момент: createdValues не является потоко-безопасным, и для параллельной работы его следует заменить на synchronizedList, речь о котором пойдёт далее. Если пара потоков попытается одновременно добавить новый элемент в список, то один из них может быть утерян! 3. Параллельные коллекции Существует несколько способов обработки синхронизированных коллекций в Java. Collections.synchronizedList(List<K>) является классом, который может использоваться для выполнения регулярных операций со списками (add, addAll, get, set, ...) методом синхронизации. Существуют также аналогичные реализации Map, Set и другие. CopyOnWriteArrayList может использоваться для обеспечения поточно-ориентированных операций модификации в List. Любая операция изменения сначала копирует весь список в локальную переменную с помощью итератора, выполняет действие, а затем заменяет его исходным списком. Эта коллекция примечательна тем, что обход списка не требует синхронизации. HashTable обеспечивает синхронизированный доступ к Map. Следует отметить, что синхронизируются только отдельные функции Map. Например, put не в их числе, поэтому безопасность потока на всей карте не гарантируется. ConcurrentHashMap<K,V> можно использовать для обеспечения безопасности потоков во всех методах HashMap. Важно отметить, что операции чтения (например, get) не блокируют Map целиком. Deadlock Код ниже не работает, а точнее никогда не остановится. Догадаетесь почему? data class Human(val name:String) { @Synchronized fun sayHi(to: Human){ println("$name saying hi to ${to.name}") Thread.sleep(500) to.sayHiBack(this) } @Synchronized fun sayHiBack(to: Human){ println("$name saying hi back to ${to.name}") } } fun main() { val adam = Human("adam") val eve = Human("eve") val adamThread = Thread { adam.sayHi(eve) }.apply { start() } val eveThread = Thread { eve.sayHi(adam) }.apply { start() } adamThread.join() eveThread.join() } У нас есть два человека − Адам и Ева. Когда один говорит другому привет, второй вынужден ответить ему тем же. Когда поток Адама запускается, он пытается сказать «привет» Еве, блокируя собственный объект с помощью @Synchronized и ждёт 500 мс. Тем временем начинается поток Евы, по той же схеме. По истечении 500мс, Адам вызывает Eve.sayHiBack, то есть пытается получить блокировку ещё и над объектом eve. Ева также ждёт освобождения доступа к adam.sayHiBack. Таким образом, оба потока просто останавливаются в бесконечном ожидании. При написании синхронизированного кода нужно подумать обо всех возможных ситуациях в блокировках-разблокировках. Вы можете создать такую же взаимную блокировку, как в примере выше, используя другие средства синхронизации, замки. Заключение В этой статье были рассмотрены различные методы синхронизации и обеспечения потокобезопасности, коллекции для параллельной работы и вспомогательные классы Java и Kotlin.
  3. ТОП-6 алгоритмов сортировки на Java для новичков Изучение алгоритмов сортировки на языке Java поможет не изобретать велосипеды и быстро выскочить на лесенку карьерного роста. Задействование алгоритмов сортировки поможет нам упорядочить массивы Java. Для понимания: сортировка чисел от наименьшего к большему или наоборот, а также лексикографический порядок – это примеры алгоритмов сортировки, способные упорядочить Java строки и числа Сортировка пузырьком Слышали о сортировке пузырьком? Его популярность обусловлена простотой, наглядностью и, конечно, названием. Алгоритм просматривает массив и сравнивает каждую пару соседних элементов. Когда он встречает пару элементов, расположенных не по порядку, происходит замена двух элементов местами. Остается вопрос: как узнать, что все элементы упорядочены? В этом случае очередная итерация пройдет без замены соседних элементов. Вот шаги для сортировки массива чисел от наименьшего к большему: 4 2 1 5 3: два первых элемента расположены в массиве в неверном порядке. Меняем их. 2 4 1 5 3: вторая пара элементов тоже «не в порядке». Меняем и их. 2 1 4 5 3: а эти два элемента в верном порядке (4 < 5), поэтому оставляем как есть. 2 1 4 5 3: очередная замена. 2 1 4 3 5: результат после одной итерации. Для полной сортировки нужен еще один шаг. Третья итерация пройдет уже без замены. Так вы поймете, что массив отсортирован. Но причём тут пузырьки? Посмотрите снова на пример, и вы увидите, что алгоритм как бы смещается вправо. По этому поведению элементов в массиве и возникла аналогия с «пузырьками», всплывающими на «поверхность». Реализация Функция входит в цикл while, в котором проходит весь массив и меняет элементы местами при необходимости. Массив в алгоритме считается отсортированным. При первой замене доказывается обратное и запускается еще одна итерация. Цикл останавливается, когда все пары элементов в массиве пропускаются без замен: public static void bubbleSort(int[] array) { boolean sorted = false; int temp; while(!sorted) { sorted = true; for (int i = 0; i < array.length - 1; i++) { if (array > array[i+1]) { temp = array; array = array[i+1]; array[i+1] = temp; sorted = false; } } } } Будьте осторожны с формулировкой условия замены! Например, при условии a >= a[i+1]алгоритм войдет в бесконечный цикл, потому что для равных элементов это условие остается true: отсюда следует бесконечная замена Временная сложность Рассмотрим наихудший сценарий. Вот в чем вопрос: сколько итераций нужно для сортировки всего массива? Пример: 5 4 3 2 1 При первой итерации 5 «всплывает на поверхность», при этом остальные элементы остаются в порядке убывания. Если вы хотите получить отсортированный массив, придется делать по одной итерации для каждого элемента, кроме 1, и еще одну итерацию для проверки, что в сумме составляет 5 итераций. Расширьте это утверждение для массива из nэлементов, и получите n итераций. В коде это означает, что цикл while будет запускаться максимум n раз. Каждая n-ая итерация по всему массиву (цикл for в коде) означает, что временная сложность в наихудшем случае будет равна O(n ^ 2). Сортировка вставками Этот алгоритм разделяет оригинальный массив на сортированный и несортированный подмассивы. Длина сортированной части равна 1 в начале и соответствует первому (левому) элементу в массиве. После этого остается итерировать массив и расширять отсортированную часть массива одним элементом с каждой новой итерацией. После расширения новый элемент помещается на свое место в отсортированном подмассиве. Это происходит путём сдвига всех элементов вправо, пока не встретится элемент, который не нужно двигать. В приведенном ниже массиве жирная часть отсортирована в порядке возрастания. Посмотрите что произойдет в этом случае: 3 5 7 8 4 2 1 9 6: выбираем 4 и помним, что это элемент, который нужно вставить. 8 > 4, поэтому сдвигаем. 3 5 7 x 8 2 1 9 6: здесь x – нерешающее значение, так как элемент будет перезаписан (на 4, если это подходящее место, или на 7, если смещение). 7 > 4, поэтому сдвигаемся. 3 5 x 7 8 2 1 9 6 3 x 5 7 8 2 1 9 6 3 4 5 7 8 2 1 9 6 Теперь вы видите, что отсортированная часть дополнилась элементом. Каждая следующая итерация делает то же самое, и к концу вы получите отсортированный массив! Реализация public static void insertionSort(int[] array) { for (int i = 1; i < array.length; i++) { int current = array; int j = i - 1; while(j >= 0 && current < array[j]) { array[j+1] = array[j]; j--; } // в этой точке мы вышли, так что j так же -1 // или в первом элементе, где текущий >= a[j] array[j+1] = current; } } Временная сложность Вернемся к худшему сценарию – к массиву, отсортированному в убывающем порядке. В этом случае каждая итерация сдвигает отсортированный массив на единицу O(n). Придется делать это для каждого элемента в каждом массиве, что приведет к сложности равной O(n ^ 2). Сортировка выбором Сортировка выбором тоже разделяет массив на сортированный и несортированный подмассивы. Но на этот раз сортированный подмассив формируется вставкой минимального элемента не отсортированного подмассива в конец сортированного, заменой: 3 5 1 2 4 1 5 3 2 4 1 2 3 5 4 1 2 3 5 4 1 2 3 4 5 1 2 3 4 5 Реализация В каждой итерации вы предполагаете, что первый неотсортированный элемент минимален и итерируете по всем оставшимся элементам в поисках меньшего. После нахождения текущего минимума неотсортированной части массива вы меняете его местами с первым элементом, и он уже часть отсортированного массива: public static void selectionSort(int[] array) { for (int i = 0; i < array.length; i++) { int min = array; int minId = i; for (int j = i+1; j < array.length; j++) { if (array[j] < min) { min = array[j]; minId = j; } } // замена int temp = array; array = min; array[minId] = temp; } } Временная сложность При поиске минимума для длины массива проверяются все элементы, поэтому сложность равна O(n). Поиск минимума для каждого элемента массива равен O(n^2). Сортировка слиянием Сортировка слиянием эффективнее, чем примеры алгоритмов сортировки, представленные выше, благодаря использованию рекурсии и подходу «разделяй и властвуй». Массив делится на два подмассива, а затем происходит: Сортировка левой половины массива (рекурсивно) Сортировка правой половины массива (рекурсивно) Слияние На схеме показана работа рекурсивных вызовов. Для массивов, отмеченных стрелкой вниз, вызывается функция. Во время слияния возвращаются массивы со стрелкой вверх. Всё просто: мы следуем за стрелкой вниз к нижней части дерева, а затем возвращаемся и объединяем. В примере массив 3 5 4 2 1 делится на 3 5 4и 2 1 и так далее. При достижении «дна» начинается объединение и сортировка. Реализация В главную функцию передаются left и right – индексы подмассивов для сортировки, крайние слева и справа. Изначально они имеют значения 0и array.length-1, в зависимости от реализации. Основа нашей рекурсии гарантирует, что мы выйдем, когда закончим, или когда left и rightвстретятся друг с другом. Мы находим среднюю точку mid и рекурсивно сортируем подмассивы слева и справа от середины, в итоге объединяя наши решения. Возможно, вы вспомните дерево и спросите: почему мы не передаем два меньших массива? Ответ прост: это не нужно и вызовет огромное потребление памяти для очень длинных массивов. Достаточно следовать индексам не нарушая логики дерева рекурсии: public static void mergeSort(int[] array, int left, int right) { if (right <= left) return; int mid = (left+right)/2; mergeSort(array, left, mid); mergeSort(array, mid+1, right); merge(array, left, mid, right); } Для сортировки двух подмассивов в один нужно вычислить их длину и создать временные массивы, в которые будем копировать. Так можно свободно изменять главный массив. После копирования мы проходим по результирующему массиву и назначаем текущий минимум. Помните, что наши подмассивы отсортированы? Теперь нужно просто выбрать наименьший из двух элементов, которые еще не были выбраны, и двигать итератор для этого массива вперед: void merge(int[] array, int left, int mid, int right) { // вычисляем длину int lengthLeft = mid - left + 1; int lengthRight = right - mid; // создаем временные подмассивы int leftArray[] = new int [lengthLeft]; int rightArray[] = new int [lengthRight]; // копируем отсортированные массивы во временные for (int i = 0; i < lengthLeft; i++) leftArray = array[left+i]; for (int i = 0; i < lengthRight; i++) rightArray = array[mid+i+1]; // итераторы содержат текущий индекс временного подмассива int leftIndex = 0; int rightIndex = 0; // копируем из leftArray и rightArray обратно в массив for (int i = left; i < right + 1; i++) { // если остаются нескопированные элементы в R и L, копируем минимальный if (leftIndex < lengthLeft && rightIndex < lengthRight) { if (leftArray[leftIndex] < rightArray[rightIndex]) { array = leftArray[leftIndex]; leftIndex++; } else { array = rightArray[rightIndex]; rightIndex++; } } // если все элементы были скопированы из rightArray, скопировать остальные из leftArray else if (leftIndex < lengthLeft) { array = leftArray[leftIndex]; leftIndex++; } // если все элементы были скопированы из leftArray, скопировать остальные из rightArray else if (rightIndex < lengthRight) { array = rightArray[rightIndex]; rightIndex++; } } } Временная сложность Хотите легко рассчитывать рекурсивные реализации алгоритмов сортировки? Приготовьтесь к математике :) Для вычисления временной сложности нам понадобится основная теорема о рекуррентных соотношениях. Временную сложность рекурсивных алгоритмов сортировки можно описать следующим уравнением: Здесь a – это количество меньших рекурсивных вызовов, на которые мы делим проблему, а bуказывает на входную величину рекурсивных вызовов. Остальная часть уравнения – это сложность слияния всех решений в одно конечное. Упомянутая теорема решит все за вас: Если T(n) – это время выполнения алгоритма для сортировки массива длинной n, сортировка слиянием запустится дважды для массивов длиной вполовину от оригинального. Так, если a=2, b=2, шаг слияния занимает O(n)памяти при k=1. Это означает, что уравнение для сортировки слиянием будет выглядеть так: Примените теорему, и вы увидите, что в нашем случае a=b^k, ибо 2=2^1. Значит, сложность равна O(nlog n), и это лучшая временная сложность для алгоритма сортировки. Доказано, что массив не может быть отсортирован быстрее, чем O(nlog n). Пирамидальная сортировка Для понимания работы пирамидального алгоритма сортировки нужно понять структуру, на которой он основан – пирамиду. Пирамида или двоичная куча – это дерево, в котором каждый узел состоит в отношениях с дочерними узлами. Добавление нового узла начинается с левой позиции нижнего неполного уровня. По мере движения вниз по дереву значения уменьшаются (min-heap) или увеличиваются (max-heap). Смотрите пример max-heap: А теперь представим пирамиду в виде массива: 8 5 6 3 1 2 4 Чтение графа сверху вниз здесь представлено слева направо. Мы добились того, что позиция дочернего элемента по отношению к k-ому элементу в массиве – 2\*k+1 и 2\*k+2 (при условии, что индексация начинается с 0). Проверьте сами! И наоборот, для k-го элемента дочерняя позиция всегда равна (k-1)/2. С этими знаниями вы сделаете max-heap из любого массива! Для этого проверьте каждый элемент на условие, что каждый из его дочерних элементов имеет меньшее значение. Условие верно? Тогда меняйте местами один из дочерних элементов с родительским и повторяйте рекурсию с новым родительским элементом (он может всё ещё быть больше другого дочернего). 6 1 8 3 5 2 4: оба дочерних меньше родительского, оставляем как есть. 6 1 8 3 5 2 4: 5 > 1, поэтому меняем их. Теперь рекурсивно проверяем 5. 6 5 8 3 1 2 4: оба дочерних меньше 5, поэтому пропускаем. 6 5 8 3 1 2 4: 8 > 6, поэтому меняем их. 8 5 6 3 1 2 4: мы получили пирамиду, изображенную выше! Вы научились строить пирамиду из массива, все остальное гораздо проще! Поменяйте местами корень пирамиды с концом массива, и сократите массив на единицу. Постройте кучу из сокращенного массива и повторяйте процесс: 8 5 6 3 1 2 4 4 5 6 3 1 2 8: замена 6 5 4 3 1 2 8: сортировка 2 5 4 3 1 6 8: замена 5 2 4 2 1 6 8: сортировка 1 2 4 2 5 6 8: замена И так далее. Видите закономерность? Реализация static void heapify(int[] array, int length, int i) { int leftChild = 2*i+1; int rightChild = 2*i+2; int largest = i; // если левый дочерний больше родительского if (leftChild < length && array[leftChild] > array[largest]) { largest = leftChild; } // если правый дочерний больше родительского if (rightChild < length && array[rightChild] > array[largest]) { largest = rightChild; } // если должна произойти замена if (largest != i) { int temp = array; array = array[largest]; array[largest] = temp; heapify(array, length, largest); } } public static void heapSort(int[] array) { if (array.length == 0) return; // Строим кучу int length = array.length; // проходим от первого без ответвлений к корню for (int i = length / 2-1; i >= 0; i--) heapify(array, length, i); for (int i = length-1; i >= 0; i--) { int temp = array[0]; array[0] = array; array = temp; heapify(array, i, 0); } } Временная сложность Посмотрите на функцию heapify() – кажется, что все делается за O(1), верно? Но нет же: все портит рекурсивный вызов! Готовы посчитать, сколько вызовов нужно в наихудшем сценарии? В худшем случае рекурсивный вызов дойдет до самой вершины пирамиды прыжками к родителям каждого узла в отношении i/2. Всего потребуется log n прыжков до вершины, значит, сложность равна O(log n). Это ещё не всё! В связи с циклами for, которые итерируют весь массив, сложность heapSort() равна O(n). Это дает нам суммарную сложность пирамидальной сортировки O(nlog n). Быстрая сортировка На этом участнике нашего топа мы закончим разбирать примеры алгоритмов сортировки. Перед вами очередной алгоритм техники «разделяй и властвуй». Он выбирает один элемент массива в качестве стержня и сортирует остальные элементы вокруг (меньшие элементы налево, большие направо). Так соблюдается правильная позиция самого «стержня». Затем алгоритм рекурсивно повторяет сортировку для правой и левой частей. Реализация static int partition(int[] array, int begin, int end) { int pivot = end; int counter = begin; for (int i = begin; i < end; i++) { if (array < array[pivot]) { int temp = array[counter]; array[counter] = array; array = temp; counter++; } } int temp = array[pivot]; array[pivot] = array[counter]; array[counter] = temp; return counter; } public static void quickSort(int[] array, int begin, int end) { if (end <= begin) return; int pivot = partition(array, begin, end); quickSort(array, begin, pivot-1); quickSort(array, pivot+1, end); } Временная сложность Временную сложность алгоритма быстрой сортировки можно описать следующим уравнением: В наихудшем сценарии наибольший и наименьший элементы всегда выбираются в качестве стержня. Тогда уравнение приобретает вид: Получается O(n^2). На фоне алгоритмов сортировки со сложностью O(nlog n), выглядит не очень :( На практике быстрая сортировка применяется широко. Судите сами: у алгоритма очень хорошее среднее время запуска, также равное O(nlog n), он эффективен для больших потоков ввода. И на этом преимущества не заканчиваются! Алгоритм не занимает дополнительного пространства, вся сортировка происходит «на месте», отсутствуют затратные вызовы распределения, из-за чего его часто предпочитают сортировке слиянием. На этом всё!
  4. Oh1

    Лучшие IDE для Java

    Лучшие IDE для Java Лучшая бесплатная IDE: NetBeans NetBeans - мощнейшая среда разработки с открытым исходным кодом, ориентированная на интернет, мобильные и настольные приложения. Работает с Linux, Windows, MacOS и даже Oracle Solaris. Несмотря на то, что NetBeans позволяет работать на нескольких языках, в среде разработчиков она считается Java-ориентированной. Она прекрасно взаимодействует с JPA, JSP, Struts, Spring и библиотекой Hibernate. Лучшая коммерческая IDE: IntelliJ IDEA По правде говоря, IntelliJ IDEA распространяется в двух версиях, одна из которых совершенно бесплатная - Free Community Edition. Причём для начинающего разработчика данного пакета хватит с головой. В частности, IDE Android Studio, речь о которой пойдёт чуть позднее, основана именно на этой версии. В платной же версии вы получаете поддержку фреймворков Spring (Spring MVC framework, Spring Security, Spring Boot, Spring Integration и т. д.), Node.js, Angular React, Grails, возможность использовать дополнительные языки (javascript, typescript, coffeescript) и взаимодействовать почти со семи популярными серверами (Tomcat, TomEE, GlassFish, JBoss, WildFly, Weblogic, WebSphere, Geronimo, Virgo и т. д.). Самая популярная IDE: Eclipse Точную цифру привести практически невозможно, но практически любой Java-разработчик с опытом работы более 2 лет сталкивался с этой IDE. Победителем в этой номинации Eclipse удалось стать благодаря большому сообществу, тонне полезной информации и бесчисленному количеств плагинов. Как и с предыдущими экземплярами, Eclipse поддерживает несколько языков, но воспринимается как приверженец Java. Cамая универсальная IDE: JDeveloper Ещё один продукт от Oracle с массой преимуществ, среди которых поддержка системы контроля версий и облачного сервиса Oracle, он упакован SQL Developer, PL / SQL обработчиком запросов, WebLogic Server, редакторами HTML, CSS, JavaScript, JSF, JSP, WSDL и ещё огромным количеством всевозможных полезностей. Лучшая для Android: Android Studio Было бы странно, если победителем в этой номинации стала какая-нибудь другая IDE. Помимо всех возможностей, который вам дарит исходная IDE IntelliJ IDEA, Android Studio включает в себя немало надстроек от Google, как чисто визуальных (макеты, форматы, GPU профайлер), так и функциональных (JUnit 4 и Firebase Test Lab для тестирования и отладки, система сборки Gradle, Instant Run). Лучшая IDE для обучения: DrJava Именно к такому выводу пришла команда разработчиков под названием JavaPLT, представляющие университет Райса. Оно и неудивительно, учитывая, что DrJava - их детище. Впрочем, оставив шутки в стороне, стоит признать, что DrJava действительно прекрасно подойдёт новичкам, ведь данная IDE даже не ставит своей целью соперничество с выше названными. Главное её преимущество - предельно быстрая настройка и переход к непосредственному написанию кода. В качестве конкурентов можно на схожих условиях рассмотреть BlueJ, JGrasp и Greenfoot. Самая перспективная IDE: MyEclipse Приветственная надпись на странице скачивания гласит “The best Java EE IDE enhanced for the full stack developer”. Что ж, это весьма нескромно, совсем не подкреплено фактами, но по правде говоря - недалеко от истины. В сущности, MyEclipse - это Eclipse, где всё изначально “привинчено”, “допилено” и ещё немного расширено. К услугам разработчика предлагается несколько версий, две основные - стандартная и профессиональная. Стандартная - это как раз Eclipse в новой оболочке, а Professional содержит мобильный веб-симулятор, редактор картинок, UML-редактор, шаблоны, надстройки - в общем, всё, что сделает создание продукта значительно проще.
  5. Мониторинг файлов вместе с Java NIO Небольшой урок о том, как с помощью пакета java.nio.file написать класс для наблюдения за изменениями состояния файлов в директории. Java NIO или Java New I/O это крайне полезный пакет, позволяющий использовать асинхронный ввод/вывод. Сегодня с помощью java.nio.file, следуя паттерну Observer, мы реализуем свой класс для наблюдения за состоянием файлов в папке. Наш план: Первым делом создадим WatchService. Потом переменную Path, указывающую на папку, которую планируем мониторить. Далее бесконечный цикл наблюдения. Когда происходит интересующее нас событие, класс WathKeyпомещает его в очередь наблюдателя. После обработки события мы должны вернуть ключ в состояние готовности, вызвав метод reset(). Если метод вернёт false, то ключ больше не действителен, цикл можно завершить. WatchService watchService = FileSystems.getDefault().newWatchService(); Path path = Paths.get("c:\\directory"); //будем следить за созданием, изменение и удалением файлов. path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); boolean poll = true; while (poll) { WatchKey key = watchService.take(); for (WatchEvent<?> event : key.pollEvents()) { System.out.println("Event kind : " + event.kind() + " - File : " + event.context()); } poll = key.reset(); } Данный код должен вывести в консоль следующее: Event kind : ENTRY_CREATE - File : file.txt Event kind : ENTRY_DELETE - File : file.txt Event kind : ENTRY_CREATE - File : test.txt Event kind : ENTRY_MODIFY - File : test.txt Watch Service API довольно низкоуровневая штука, мы реализуем на его основе свой более высокоуровневый API. Начнём с написания класса FileEvent. Так как это объект состояния события, его необходимо унаследовать от EventObjectи передать в конструктор ссылку на файл, за котором будем наблюдать. FileEvent.java import java.io.File; import java.util.EventObject; public class FileEvent extends EventObject { public FileEvent(File file) { super(file); } public File getFile() { return (File) getSource(); } } Теперь создаём интерфейс слушателя FileListener, наследуемый отjava.util.EventListener. Этот интерфейс должны реализовать все слушатели, которые будут подписываться на события нашей папки. FileListener.java import java.util.EventListener; public interface FileListener extends EventListener { public void onCreated(FileEvent event); public void onModified(FileEvent event); public void onDeleted(FileEvent event); } Наконец, создаём класс, который будет хранить в себе список слушателей, подписанных на папку. Назовём его FileWatcher. public class FileWatcher { protected List<FileListener> listeners = new ArrayList<>(); protected final File folder; public FileWatcher(File folder) { this.folder = folder; } public List<FileListener> getListeners() { return listeners; } public FileWatcher setListeners(List<FileListener> listeners) this.listeners = listeners; return this; } } Хорошей идеей будет реализовать класс FileWatcher как Runnable, чтобы иметь возможность запускать наблюдение в виде демона, если указанная папка существует. FileWatcher.java public class FileWatcher implements Runnable { public void watch() { if (folder.exists()) { Thread thread = new Thread(this); thread.setDaemon(true); thread.start(); } } @Override public void run() { // пока оставим без реализации } } Так как в методе run() будут создаваться объекты WatchService, использующие внешние ресурсы (ссылки на файлы), все события будем хранить в статическом списке. Такая реализация позволит вызвать метод close() из любого потока, ждущего ключ. А также вызвать исключение ClosedWatchServiceException, чтобы отменить наблюдение без утечки памяти. @Override public void contextDestroyed(ServletContextEvent event) { for (WatchService watchService : FileWatcher.getWatchServices()){ try { watchService.close(); } catch (IOException e) {} } } public class FileWatcher implements Runnable { protected static final List<WatchService> watchServices = new ArrayList<>(); @Override public void run() { try (WatchService watchService = FileSystems.getDefault().newWatchService()) { Path path = Paths.get(folder.getAbsolutePath()); path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); watchServices.add(watchService); boolean poll = true; while (poll) { poll = pollEvents(watchService); } } catch (IOException | InterruptedException | ClosedWatchServiceException e) { Thread.currentThread().interrupt(); } } protected boolean pollEvents(WatchService watchService) throws InterruptedException { WatchKey key = watchService.take(); Path path = (Path) key.watchable(); for (WatchEvent<?> event : key.pollEvents()) { notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile()); } return key.reset(); } public static List<WatchService> getWatchServices() { return Collections.unmodifiableList(watchServices); } } Когда происходит интересующее нас событие и путь корректен, мы уведомляем слушателей о событии. Если была создана новая директория, то для неё будет инициализирован новый экземпляр FileWatcher. FileWatcher.java public class FileWatcher implements Runnable { protected void notifyListeners(WatchEvent.Kind<?> kind, File file) { FileEvent event = new FileEvent(file); if (kind == ENTRY_CREATE) { for (FileListener listener : listeners) { listener.onCreated(event); } if (file.isDirectory()) { // создаем новый FileWatcher для отслеживания новой директории new FileWatcher(file).setListeners(listeners).watch(); } } else if (kind == ENTRY_MODIFY) { for (FileListener listener : listeners) { listener.onModified(event); } } else if (kind == ENTRY_DELETE) { for (FileListener listener : listeners) { listener.onDeleted(event); } } } } Полная реализация класса FileWatcherбудет выглядеть так: FileWatcher.java import static java.nio.file.StandardWatchEventKinds.*; import java.io.File; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class FileWatcher implements Runnable { protected List<FileListener> listeners = new ArrayList<>(); protected final File folder; protected static final List<WatchService> watchServices = new ArrayList<>(); public FileWatcher(File folder) { this.folder = folder; } public void watch() { if (folder.exists()) { Thread thread = new Thread(this); thread.setDaemon(true); thread.start(); } } @Override public void run() { try (WatchService watchService = FileSystems.getDefault().newWatchService()) { Path path = Paths.get(folder.getAbsolutePath()); path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); watchServices.add(watchService); boolean poll = true; while (poll) { poll = pollEvents(watchService); } } catch (IOException | InterruptedException | ClosedWatchServiceException e) { Thread.currentThread().interrupt(); } } protected boolean pollEvents(WatchService watchService) throws InterruptedException { WatchKey key = watchService.take(); Path path = (Path) key.watchable(); for (WatchEvent<?> event : key.pollEvents()) { notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile()); } return key.reset(); } protected void notifyListeners(WatchEvent.Kind<?> kind, File file) { FileEvent event = new FileEvent(file); if (kind == ENTRY_CREATE) { for (FileListener listener : listeners) { listener.onCreated(event); } if (file.isDirectory()) { new FileWatcher(file).setListeners(listeners).watch(); } } else if (kind == ENTRY_MODIFY) { for (FileListener listener : listeners) { listener.onModified(event); } } else if (kind == ENTRY_DELETE) { for (FileListener listener : listeners) { listener.onDeleted(event); } } } public FileWatcher addListener(FileListener listener) { listeners.add(listener); return this; } public FileWatcher removeListener(FileListener listener) { listeners.remove(listener); return this; } public List<FileListener> getListeners() { return listeners; } public FileWatcher setListeners(List<FileListener> listeners) { this.listeners = listeners; return this; } public static List<WatchService> getWatchServices() { return Collections.unmodifiableList(watchServices); } } Последний штрих – создание класса FileAdapter, простейшей реализации интерфейса FileListener. FileAdapter.java public abstract class FileAdapter implements FileListener { @Override public void onCreated(FileEvent event) { //реализация не предусмотрена } @Override public void onModified(FileEvent event) { //реализация не предусмотрена } @Override public void onDeleted(FileEvent event) { //реализация не предусмотрена } } Чтобы убедиться в работоспособности написанного кода, можно использовать несложный класс FileWatcherTest. FileWatcherTest.java import static org.junit.Assert.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; public class FileWatcherTest { @Test public void test() throws IOException, InterruptedException { File folder = new File("src/test/resources"); final Map<String, String> map = new HashMap<>(); FileWatcher watcher = new FileWatcher(folder); watcher.addListener(new FileAdapter() { public void onCreated(FileEvent event) { map.put("file.created", event.getFile().getName()); } public void onModified(FileEvent event) { map.put("file.modified", event.getFile().getName()); } public void onDeleted(FileEvent event) { map.put("file.deleted", event.getFile().getName()); } }).watch(); assertEquals(1, watcher.getListeners().size()); wait(2000); File file = new File(folder + "/test.txt"); try(FileWriter writer = new FileWriter(file)) { writer.write("Some String"); } wait(2000); file.delete(); wait(2000); assertEquals(file.getName(), map.get("file.created")); assertEquals(file.getName(), map.get("file.modified")); assertEquals(file.getName(), map.get("file.deleted")); } public void wait(int time) throws InterruptedException { Thread.sleep(time); } } Несмотря на кажущуюся простоту, наблюдатели – мощный инструмент автоматизации процессов. С помощью них, например, можно автоматически перезапускать скрипты на продакшене, если один из них был изменён. Уменьшение человеческого фактора – благо для программиста.
  6. Полезные советы при изучении Java Пока думаете, как построить свои занятия, учтите пару подсказок, которые помогут учиться быстрее и лучше. Эти советы пригодятся и начинающим, и даже опытным программистам. Не бойтесь спрашивать людей. Проясним положение: вы вряд ли добьётесь успеха, обучаясь в изоляции. Это ужасный подход. Важно уделять чрезвычайное внимание изучению, но также убедитесь, что вы обращаетесь к остальным при необходимости. Кое-какие проблемы, с которыми столкнётесь, решаются при обсуждении с другим человеком. Почём знать, он встречал и разбирал аналогичную проблему. Применяйте опыт наставников. В блоге часто задают вопрос, что делает моих учеников такими преуспевающими. Я улыбаюсь и говорю: «Ну, я заставляю повторять мой собственный опыт». У великого наставника богатейший опыт, потому используйте его, чтобы накопить личные знания. Учитесь на этом. Присоединяйтесь к форумам и сообществам для программирования. Разработчики найдут массу подходящего. Вступайте в оживлённые, потому что так освоите кучу вещей. Вы будете учиться на опыте других, задавать вопросы, а также обсуждать и искать решения возникающих проблем. Некоторые из них включают в себя Java Forum, Java World, CodeGym Help и подфорумы программирования на Reddit (например, learnjava и learnprogramming). Добавлю: поймите, что на этих форумах и в сообществах обитают разнообразные персонажи. Не ожидайте увидеть одних хороших. На самом деле, попадаются случаи, когда вы в результате разочаровываетесь и запутываетесь больше, чем раньше. Мы живём в эру троллинга, поэтому вам придётся уметь не замечать его.
  7. 10 популярных вопросов с Java-собеседований Есть вопросы, которые постоянно попадаются на собеседованиях, и на которые ты всё время забываешь ответы? Вот 10 самых распространенных вопросов для джавистов! Уточнение: здесь не расшифрован ответ полностью, а лишь предоставляется краткое описание. Развернутую версию ответа тебе придется искать в сети самостоятельно. ООП На Java пишут только с использованием этой парадигмы. Мысль писать код в процедурном стиле следует откинуть и больше не вспоминать. Идеально, если ты придешь к использованию ООП самостоятельно, понимая его удобство. Основные принципы ООП – абстракция, наследование, инкапсуляция и полиморфизм. Вместе с наследованием используйте делегацию, композицию и агрегацию. И, пожалуйста, Don’t repeat yourself (DRY)! Подробнее расписано в тематической статье. Также почитай о принципе подстановки Барбары Лисков. Почему Java? Тут все просто. За счет кроссплатформенности, многопоточности, удобного ООП, элементарного синтаксиса и мощи Sun/Oracle Java стала крутой и популярной. На этом языке написана уйма серверов, крупных проектов и даже микроволновок. Не поверишь, но в качестве языка для бекенда Java превосходит PHP на голову. На последнем написано довольно мало проектов, не считая CMS. Самые большие системы основаны на Java. Есть jsoup для парсинга HTML, есть RestTemplate для опроса рестов, есть Cucumber и Selenium для имитации действий пользователя в браузере. Плюс ты всегда можешь создать гибридное решение PHP + Java. Как работает Java? Основная проблема большинства новичков – переход от теоретической базы о языках программирования к "Hello World!", а ведь именно основы Java хранят в себе все секреты. JDK (Java Development Kit) – набор необходимых инструментов, позволяющих развернуть Java на любой платформе, программировать и компилировать код. JRE (Java Runtime Environment) – это то место, где находится JVM (виртуальная машина), весь запущенный рантайм и загруженные классы, т. е. все, что нужно для работы и функционирования кода. JVM (Java Virtual Machine) – основная штука, с помощью которой кроссплатформенность в принципе возможна. Все примерно так: написанный код компилируется в байт-код, который может запускаться на этой виртуалке; виртуалка интерпретирует весь код в машинный код для конкретной платформы. JIT (Just-in-time) – оптимизированная штука от Java, чем-то похожая на precompiled header из C++. JNA (Java Native Access) – способ из Java вызывать нативный код для решения некоторых задач. Разница между интерфейсом и абстрактным классом Лучший способ продемонстрировать разницу между этими фичами – раскрыть секреты Java и рассказать, что умеет делать каждый из “конкурсантов”. Абстрактные классы могут иметь константы и переменные, определенные и объявленные методы с любыми модификаторами доступа. Интерфейсы включают в себя только константы и только объявление методов с публичным доступом. Интерфейсы участвуют в имитации множественного наследования, а в случае с классом, он может наследоваться только от одного класса-родителя. Пожалуй, самая важная тема для понимания, не осознав ее, тебе будет очень сложно вникнуть в суть Java. Ключевое слово static Статическими могут быть как переменные, так и методы. Переменные экземпляров, объявленные как static, являются глобальными переменными. При объявлении объектов не создается никаких копий статической переменной. Вместо этого все экземпляры класса совместно используют одну и ту же статическую переменную. Статические методы могут вызывать только другие статические методы, умеют обращаться только к статическим переменным и не могут ссылаться на this и super (о них тоже почитай). final Данное ключевое слово может применяться к методам, классам и переменным. Если это: Класс, то от него нельзя наследоваться. Метод, то его нельзя переопределять в саб-классах. Переменная, то это константа. Примитивы и объекты К примитивам Java относится byte, short, char, int, long, float, double, boolean, а кастомные типы данных (класс String) формируются уже из примитивов. Примитивы несут в себе значение, а не ссылки, как многие заблуждаются. Забегая вперед, можно сказать, что для примитивов юзается стек, а для объектов – куча. Вот такой обширный вопрос. Обязательно разберись: в типах памяти для чего какой тип используется боксинг/анбоксинг классы-обертки Передача по ссылке или по значению В С++ и С есть понятие чистых указателей и ссылок. В языке Java (являющимся улучшенной версией C++) указатели сделали неявными для упрощения кода. Когда создается объект, то ссылка на объект хранит в себе адрес в памяти того объекта, который был создан с помощью оператора new. Если такую ссылку передать в качестве параметра функции, то в том месте, где она передается, будет создана копия, поэтому передача произойдет по значению. Но данная копия будет ссылаться на тот адрес, на который ссылается и оригинал. Если произвести действия над этой копией, оригинал тоже будет изменен. Это как указатель в C++, только разыменование происходит автоматически и неявно. Такое поведение работает только в случае с объектами. С примитивами все проще: передав примитив в метод и изменив его там, будет изменяться копия, и это не повлияет на глобальные значения. Это важно! Анонимные классы Существуют обычные классы и вложенные. Вложенные делятся еще на несколько видов, и среди них есть анонимный внутренний класс. Если коротко, то это класс без имени, который находится внутри другого класса. Перегрузка и переопределение Переопределение позволяет взять какой-то метод родительского класса и написать в каждом классе-наследнике свою реализацию этого метода. Чтобы понять перегрузку, нужно запомнить, что тип и/или число параметров в каждом из перегружаемых классов должно быть разным. Эта тема – ключ к пониманию, как работает полиморфизм. Итого Если ты в конце разберешься с этой подборкой и сможешь на каждый из вопросов привести пример, то ты освоил основной пласт информации, до которого обязательно докопаются на собеседовании по Java ;)
  8. Главный язык для Android разработчика в 2020 году Мы сравнили два самых популярных языка программирования под Android. Кто из них вышел победителем? Читайте! Первый язык для Android. Восемь лет язык Java занимал главенствующую позицию в Android разработке. В 2016 его начал оттеснять молодой Kotlin, созданный компанией JetBrains, а уже в 2019 Kotlin получил статус первого языка платформы. Но в Google акцентировали внимание на то, что Java не потеряет поддержку. Давайте сравним эти языки по пунктам. Главные преимущества Java: Java уверенно держит первое место в рейтинге самых распространенных языков. Большое и опытное комьюнити трудно переоценить. Масса доступных книг, курсов и отдельных туториалов. Стабильное время компиляции. К Kotlin долго были претензии по данному пункту. Статические члены. Обширная база библиотек. Наличие проверяемых исключений. Java является доминирующим языком по числу приложений. Главные преимущества Kotlin: Полная совместимость с Java. Из классов Kotlin можно вызывать методы Java и наоборот. Null safety. Исправление одной из главных проблем Java – бесконечных NullPointerExeption. Функции расширения. Удобная работа со строковыми шаблонами. “Ленивые” свойства. Геттеры, вычисляемые в моменты вызова. Наличие Singleton Object с ленивой инициализацией на уровне языка. Удобные лямбда выражения и инлайн функции. Наличие Data class. Делегированные свойства. Умное приведение и автовыведение типов. Корутины – мощный инструмент многопоточного программирования. На данный момент Kotlin развивается динамичнее главного конкурента. Отдельно обратите внимание на лаконичность Kotlin. Ниже я приведу пример простого класса Java с четырьмя полями, конструктором, геттерами и сеттерами : public class JavaClass { private String id; private String name; private int numberOfUses; private String imageUrl; public JavaClass( String id, String name, int numberOfUses, String imageUrl) { this.id = id; this.name = name; this.numberOfUses = numberOfUses; this.imageUrl = imageUrl; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNumberOfUses() { return numberOfUses; } public void setNumberOfUses(int numberOfUses) { this.numberOfUses = numberOfUses; } public String getImageUrl() { return imageUrl; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl;} } А вот всё то же самое на Kotlin: class KotlinClass( var id: String, var name: String, var numberOfUses: Int, var imageUrl: String ) Что в итоге? Рынок вакансий не может определиться. Все чаще появляется требование владеть обоими языками. В 2019 число новых Kotlin-приложений в Play Market перевалило порог в 50% от общего. Тенденция перехода индустрии на Kotlin очевидна, но процесс медленный. Тем не менее, огромное число проектов на поддержке не даст Java уйти в обозримом будущем. Если вы новичок в мобильной разработке и хотите войти в профессию максимально быстро, выбирайте Kotlin: он проще в изучении и использовании. Опытным разработчикам стоит поглядывать на оба языка и держать руку на пульсе.
  9. Разработка программного обеспечения для начинающих Разработка программного обеспечения интересна как программистам, так и тем, кто таковыми хочет стать. В статье затронуты концепции, необходимые для старта. Статья разделена на 4 части. Обратите внимание, что важные слова или словосочетания, введенные в этой серии, выделены жирным шрифтом. В конце каждого из четырех разделов будет приведена короткая викторина, проверяющая знания и подробно объясняющая некоторые моменты. Часть 1 – Что такое программирование? Самый простой и точный вариант ответа: «Программирование – это акт инструктирования компьютеров для выполнения задач». Еще его называют разработкой или кодингом. Итак, что такое компьютерная программа? ПО представляет собой последовательность инструкций, выполняемых ПК. Компьютер же – это любое устройство, способное обрабатывать код. Сюда относятся стационарные ПК, ноутбуки, планшеты, банкоматы, Raspberry Pi, серверы etc. Разработка программного обеспечения и аналогия Во-первых, примеры программирования есть даже в повседневной жизни. Вселенная довольно предсказуема: день и ночь, времена года, восход и закат. Люди проходят через такие этапы, как встреча нового дня, посещение школы, университета или работа. Мы получаем инструкции от начальников и учителей. Также существуют рецепты, следуя которым можно приготовить блюдо. Во-вторых, каждый раз, когда мы используем девайсы, встроенный в них код уже работает в фоновом режиме. Перемещение курсора с одной части экрана в другую может показаться простой задачей, но на самом деле за данный процесс отвечает немало строк написанного кода. Акт, столь же простой, как ввод букв в Google Docs, приводит к тому, что код выполняется в фоновом режиме. Это нормальные повседневные процессы, свойственные всем IT-устройствам. Компьютерные программы также являются кодом. Однако лучше не использовать слово «коды»: это непрофессионально. Естественный язык компьютера Машины пользуются своим собственным языком. Они не понимают русский, английский или испанский. Естественным языком электронного оборудования является двоичный код - 1 и 0. Он представляют собой два состояния: on (1), off (0). Осваивайте языки программирования Чтобы общаться с машинами, которые говорят на двоичном языке, мы осваиваем такие языки, которые максимально близки к нашему собственному, а именно – языки программирования. Они четко структурированы и должны быть тщательно изучены. Существуют высокий и низкий уровни. Языки программирования высокого уровня находятся дальше от машинного, чем языки низкого уровня. Это «дальше» обычно называют абстракцией. Компьютер нуждается в понимании нашего человеческого языка. Для этого понадобится переводчик. Определение переводчиков Исходный код относится к коду, написанному на выбранном языке программирования. Переводчики же несут ответственность за преобразование исходного кода в машинный язык (те самые единицы и нули). Мы можем ссылаться на двоичные файлы, такие как код объекта, программу или общепринятый сегодня термин – приложение. Переводчики могут быть любыми: интерпретаторы; компиляторы; гибриды интерпретаторов и компиляторов; ассемблеры. Интерпретаторы Чтобы разработка программного обеспечения прошла успешно, нужно понимать, что языки могут интерпретироваться. В таком случае переводчик обрабатывает исходный код по строкам и в готовой программе (приложении) также запускает каждую строку. Это означает, что интерпретируемый исходный код запускается до тех пор, пока не встретит ошибку. Затем интерпретатор перестает сообщать о таких ошибках. Python – хороший пример интерпретируемого языка программирования. Компиляторы Компиляторы работают по-разному. Они полностью конвертируют исходный код с помощью компиляции в двоичный файл. Затем выполняется двоичный код. Если в исходном варианте были ошибки, они обнаруживаются и помечаются во время компиляции. Это прерывает процесс генерации двоичного кода. Интерпретаторы работают построчно и выполняют одну линию перед тем, как перейти к следующей. Компилятор же переводит все строки программы в файл (двоичный) и выполняет его целиком. Помните определение компьютерной программы? Это последовательность инструкций для компьютера. Выполнение программы обычно называется процессом. Такие ПО используют определенные ресурсы в компьютерной системе или любом другом девайсе. К ресурсам относятся память, дисковое пространство и файловая система. Мы используем слово «run» при выполнении компьютерной программы. Время, затрачиваемое на запуск, называется временем выполнения программы. Обычно рассматриваются продукты, известные как приложения. Еще мы ассоциируем программы с платформами или средами, в которых они работают или для которых предназначены. Существуют веб-приложения, запускаемые в браузерах, есть мобильные ПО, работающие на смартфонах, а также настольные, такие как Evernote. Интерпретируемый исходный код выполняется из исходного файла, скомпилированный – преобразовывается в двоичный файл. Затем этот файл выполняется. Скомпилированный код может завершиться неудачно во время выполнения даже после успешной компиляции. Гибридные переводчики Гибридный переводчик представляет собой комбинацию интерпретатора и компилятора. Популярным гибридным языком программирования является Java. Разработка программного обеспечения на Javaудобна. Сначала исходный код компилируется в промежуточный формат, известный как Bytecode. Затем Bytecode интерпретируется и выполняется с помощью виртуальной машины. Это позволяет гибридным переводчикам запускать байт-код в различных операционных системах, делать его кроссплатформенным. Ассемблеры Ассемблер также используется для перевода низкоуровневого языка Ассемблер в двоичный, но мы сосредоточимся на языках высокого уровня. Хороший способ понять различия переводчиков – лично увидеть их работу. Просто загрузите необходимые и установите на компьютер. Часто задаваемый вопрос Вот вопрос, который обычно задают начинающие: «С какого языка начать?» Существуют сотни ЯП. Они оцениваются по популярности, комьюнити, долгосрочной поддержке, педагогике и использованию. Они также могут быть оценены по техническим параметрам. Например, являются ли они функциональными, императивными, статическими, сильными или слабо типизированными. Некоторые языки программирования предназначены исключительно для образовательных целей, а не для использования в бизнесе. Хороший пример – ЯП для детей. Также существуют мощные языки, которые легко настроить и изучить. Python – один из них. Обычно его и рекомендуют начинающим. Если вы заинтересованы в более подробном изучении вопроса, вот несколько хороших исследований. Когда вы захотите изучить новый язык, понадобится переводчик языка. Это программа, которая устанавливается и настраивается в компьютерной системе. Рекомендуем начать осваивать работу с командной строкой (CLI). Подумайте о терминале как об альтернативе графическому интерфейсу (GUI). Работая с компьютером посредством GUI, вы зависите от визуальных представлений каталогов и всего, что делаете. Но при использовании CLI вы взаимодействуете с компьютером напрямую, с помощью терминала и специальных команд. В Windows встроенный терминал представляет собой командную строку. Для пользователей Mac и Linux по умолчанию установлен терминал Bash. Чтобы использовать его в Windows, установите Git Bash или PowerShell.
  10. ТОП-5 JavaScript библиотек для визуализации данных в 2020 году Нужно красиво преподнести данные на JavaScript? Мы собрали 5 лучших инструментов, которые актуальны как сегодня, так и в грядущем году. Toast UI Chart Это опенсорсная JavaScript-библиотека для построения разнообразных диаграмм, что позволяет ее использовать для коммерческих, образовательных и личных целей абсолютно бесплатно. Над проектом потрудились знатно: выбор цветов, шаблоны стилей, анимация – все идеально. С помощью Toast UI можно создавать как простые, так и сложные проекты по визуализации, а доступный API поможет в реализации любой программной логики. Диаграммы на выходе также экспортируются в файлы .jpg и .png. Если у тебя есть опыт работы с React или VueJS, то тебе понравятся врапперы для этих фреймворков. D3.js Это самая популярная библиотека для визуализации данных в мире JavaScript. Какая бы задача ни появилась, ты сможешь воплотить ее в жизнь с помощью D3 и горы общедоступных мануалов. Несмотря на бесконечные возможности, которые предоставляет данная библиотека, она имеет высокий порог вхождения: потребуется время, чтобы научиться строить даже простые визуализации. D3.js – лучший выбор, если у тебя много пользовательской логики для отрисовки. ThreeJS Данная легковесная, популярная библиотека (45K звезд и 1K подписчиков на гитхабе) предназначена для создания 3D-анимации с использованием WebGL в контексте веб-приложений. Скрипты ThreeJS могут быть использованы совместно с HTML5, Canvas, SVG и т. д. Проект очень гибкий и будет полезен для применения в 2D визуализациях. Библиотека кроссбраузерная, работает на большинстве мобильных устройств, и ее можно тестировать онлайн. WebDataRocks WebDataRocks – это крутая онлайн-таблица, которая используется в любых проектах без каких-либо затрат. Построенная на чистом JavaScript, она плавно интегрируется во все популярные фреймворки. Элементы таблицы являются интерактивными: перетаскивай по сетке, и все пересчитается автоматом. С помощью данной фичи можно легко изменить фокус анализа в любое время. Кроме того, отчеты будут доступны на любом устройстве. PivotTable.js Одна из самых популярных опенсорсных сводных таблиц в сети. Она хорошо известна своей встроенной визуализацией тепловой карты, статистическими агрегациями и драгндроп-функциями. PivotTable.js предлагает уйму возможностей настройки, но использовать сторонние плагины все равно придется, например, для экспорта в Excel или PDF. Таким образом, потребуется время, чтобы подогнать данный инструмент к требованиям проекта. Но есть и плюс – возможность экспортировать сетку в TSV “из коробки”. Если вам нужно интегрировать PivotTabl в другие библиотеки, доступны связки для D3, C3, Plotly и Google.
  11. Функциональный JavaScript: 6 образцов кода без цикла for Лаконичные примеры того, как бывает удобно вместо циклов использовать every, map, reduce и filter. Сравниваем попарно код с применением for и функциональное решение. Зачем заменять циклы? Использование функций высшего порядка делает ваш код: более читаемым, понятнее, проще для отладки. Без лишних комментариев мы хотим показать шесть ситуаций, когда цикл – плохой выбор. К каждому примеру даётся альтернативный вариант решения через функции. 1. Перебрать все элементы и получить новый изменённый массив С циклом: Без цикла: Примечание. Если вы используете map, в процессе перебора нельзя сделать break, continue или return. Но если возникает необходимость, такие случаи обычно сводятся к применению методов every или some. 2. Перебрать все элементы и выполнить действие С циклом: Без цикла: 3. Отфильтровать массив Если использовать цикл: Используя filter: 4. Найти значение, аккумулирующее значения элементов массива Сумма чисел, если использовать цикл: Используя reduce: 5. Проверить, содержит ли массив значение Если использовать цикл: Используя some: %c в выражении будет применять стиль к тексту консоли. 6. Проверить, соответствует ли условию каждый элемент массива Если использовать цикл: Используя every: Заключение Используемый подход относится к функциональному программированию . Если понравилось, у нас есть подборка ресурсов об этой концепции и стиле написания кода.
  12. Десяток соображений, почему Java долго будет живее всех живых. 1. Универсальность Java уже почти два десятилетия входит в тройку самых популярных языков. За это время разработаны решения практически для любых сфер. Интернет вещей , блокчейн , искусственный интеллект , Облачные вычисления, – Java всё это может. В мобильной разработке до сих пор доминирует доля Андроид приложений, написанных на Java. JavaFX позволит разрабатывать десктопные приложения, а количество фреймворков для веб-разработки огромно. 2. Уйма образовательных ресурсов Java – не самый лёгкий язык в плане синтаксиса. Но это с лихвой компенсируется тонной бесплатных курсов и книг по всем мыслимым темам. На StackoverFlow даже не нужно задавать вопросы, многое отвечено заранее. Опытные же разработчики получат в распоряжение великолепную документацию. 3. Активное применение бизнесом В рейтинге TIOBE Java устойчиво держится на первом месте, как самый популярный язык среди IT-компаний. Количество открытых вакансий это подтверждает. Инвестирование времени в изучение языка даёт гарантию, что полученный навык удастся превратить в деньги. 4. Большое и надёжное сообщество Так как в разработке мы редко встречаемся с уникальными задачами, сообщество поможет сэкономить кучу времени. С чем бы ни столкнулись, наверняка кто-то уже решал эту задачу. Достаточно погуглить нужные библиотеки. Наследие сообщества позволяет разработчикам бесплатно использовать мощные IDE, менеджеры зависимостей и сервера. 5. Бесплатность В 2018-ом Oracle напрягли программистов заявлением, что Oracle JDK становится платной для использования в продакшене. На самом же деле Java осталась свободной для всех желающих, просто теперь необходимо внимательнее относиться к используемому дистрибутиву. Чтобы быть уверенным в легальности разработки, убедитесь, что применяете бесплатный Oracle OpenJDK. Кроме того, есть хорошо развивающиеся сторонние реализации. Например, оддерживаемое сообществом AdoptOpenJDK, или Coretto, созданное Amazon. 6. Cobol XXI века В контексте разговора об актуальности Java сравнение c Cobol кажется нелепым. Факт в том, что языку Cobol уже больше 60-ти лет, а вакансии с ним ещё появляются на рынке. Ведь нужно поддерживать работающие проекты. На языке Java написаны банковские системы и крупные индустриальные проекты. Даже если в недалёком будущем популярность языка спадёт, Java разработчики будут востребованы много десятилетий. 7. Полноценная платформа Java это ещё и популярная виртуальная машина JVM, на которой работают другие современные языки. Например, Scala , Groovy и Kotlin . Они привнесли в Java функциональное программирование и Null безопасность. 8. Производительность В IT-сообществе укоренился миф, что Java намного медленнее С или С++. На старте JVM и правда работала медленно . Сегодня оптимизации под нужды энтерпрайза увеличили производительность экосистемы Java на порядки, а JIT-компилятор и вовсе сократил разницу с компилируемыми языками до нуля. Эти преимущества унаследовали и языки, работающие на JVM. 9. Java стремительно развивается В течение 11 лет после того, как JDK принял Oracle, скорость развития оставляла желать лучшего. Но начиная с Java 9, компания Oracle обязалась выпускать по крупному обновлению каждые 6 месяцев, и успешно держит темп уже три года. Поэтому можно смело рассчитывать на соответствие языка трендам разработки. 10. Богатый стандартный API Java из коробки содержит обширный инструментарий. Не устанавливая дополнительных библиотек, вы можете: создавать GUI, использовать многопоточность, управлять потоками ввода и вывода, работать с сетью, получать доступ к базам данных и т. д. Все фундаментальные аспекты программирования. Заключение Конечно, выбор языков огромен. Можно пойти в сторону модных тенденций или углубиться в дебри низкоуровневого программирования, но отрицать факт, что Java будет полезен в арсенале разработчика – не получится. Если мы вас убедили, воспользуйтесь нашими подборками полезных материалов и лучших книг по Enterprise. Или сразу займитесь практикой ;)
  13. Oh1

    Разработка игр на JavaScript

    Почему JavaScript? Масса людей думает, что все крутые игры (God Of War, Assassin's Creed, Skyrim, добавь по вкусу) созданы на C++. Это отчасти так. В проекте принимают участие сотни специалистов из разных отраслей, в том числе и разработчики, юзающие другой язык – обычная распространенная практика. Некоторые классные игры написаны на “непопулярных” языках программирования, и это нормально. Если ты работаешь с JavaScript, то не нужно после этой статьи бросаться изучать “плюсы”, оставайся с JavaScript. Существуют Unity, Unreal Engine, CryEngine и прочие классные решения для создания игрушек, и если тебе удобно развлекаться с ними – пожалуйста. Поэтому нет никакой разницы, на чем ты будешь кодить, но в нашем случае речь пойдет о JS-фреймворках. Основы Прежде чем мы перейдем к рассмотрению фреймворков для создания игр, следует изучить существующие технологии. Один из вариантов – HTML5. Начиная с 5-й версии спецификации, HTML возымел тег <canvas>, который позволяет создавать контекст для рисования на веб-странице. Не нужно забывать о творении команды Khronos Group. WebGL – это веб-версия спецификации OpenGL ES, позволяющая разработчикам общаться с видеокартой через браузер (поверь, лучше не знать, как это работает). Таким образом, можно создавать 2D и 3D сцены на GPU (что эффективнее, чем на CPU). Супер! Но если взглянуть на код JavaScript, использующий эти технологии, тебе поплохеет. Поэтому давай разбираться с фреймворками, оберегающими нас от canvas и абстрагирующими от WebGL. 2D Frameworks PixiJS Этот инструмент можно назвать 2D-рендером WebGL. Это означает, что данная библиотека включает в себя множество функций, предназначенных для эффективной отрисовки 2D-сцен и объектов. Так проще сосредоточиться на создании программного кода, а хардкорные “низкоуровневые” вещи оставить разработчикам PixiJS. Это не полноценный фреймворк, но он делает свою работу настолько здорово, что многие игровые JS-фреймворки юзают его в качестве движка для рендеринга. Если ты планируешь создать что-то большее, чем анимация, то поищи дополнительные библиотеки для других частей игровой разработки (физика, масштабирование, tilemaps и т. д.). ExcaliburJS Здесь у нас полноценный игровой фреймворк, написанный на Typescript. Полная система сцен и камер, спрайты и анимации, звуки, физика и т. д. – все, что пожелаешь. Многим очень нравится API, предоставляемый ExcaliburJS, т. к. с ним уютнее. Это связано с тем, что создатели продукта из мира веб (некоторые являются веб-разработчиками, другие — DevOps), поэтому большинство шаблонов и подходов – это штуки, которые уже популярны в веб-разработке. Если тебе близка веб-разработка, попробуй этот инструмент. ImpactJS ImpactJS начал свой путь со звания “Первый фреймворк для веб-игр”. Большинство фреймворков, рассмотренных ранее, были просто экспериментами, а не коммерческим продуктом. Этот опенсорсный претендент распространяется бесплатно и поставляется с хорошим редактором уровней. Фреймворк не является самым понятным или документированным, но его надежность уже доказана. Например, разрабы из CrossCode взяли за основу форкнутую версию Impact для своего движка за его производительность и способность масштабироваться под конкретную задачу. CreateJS CreateJS – это набор модульных библиотек и HTML5-инструментов, работающих асинхронно или параллельно в зависимости от ситуации. Инструмент предоставляет все, что нужно для создания игры с нуля, с помощью отдельного модуля языка JavaScript. Например, для рендеринга можно взять PixiJS, а для работы со звуковыми материалами SoundJS и т. д. PhaserJS И напоследок самый популярный – PhaserJS. Это мощный набор инструментов для создания веб и мобильных игр. Этот фреймворк имеет огромное и активное сообщество – каждую неделю эти ребята выкладывают много новых статей, демо и туториалов, основанных на PhaserJS. Это обеспечивает отличное подспорье для людей, делающих свои первые шаги в геймдеве и нуждающихся в наставлениях. А еще, начиная с 3-й версии, это один из самых производительных игровых фреймворков. 3D Frameworks ThreeJs ThreeJs – самая популярная 3D-библиотека. Она предлагает наборы функций для выполнения общих операций, которые должны происходить в 3D-сцене. Все мероприятия происходят на более высоком уровне, чем raw WebGL, и не надо заморачиваться с горой низкоуровневых действий. BabylonJS Этот фреймворк похож на предыдущий, но имеются различия: -API меняется каждые 3 месяца, что помогает при поиске старых решений в интернете; -активное и полезное сообщество; -продуктивные и отзывчивые разработчики (у Three.js самый старый баг на GitHub датируется 2013 годом, в Babylon.js отмечен два дня назад); The playground – это отличный инструмент для быстрого “опробования” кода, объяснения проблемы и оказания помощи.