Начинаем!
История языка и предпосылки к лидерству
Почему Java так популярен? Во-первых это старый язык по меркам сферы ИТ. Ему уже более 25 лет. За это время было написано огромное количество приложений и библиотек, сформировано, наверное, самое многочисленное комьюнити программистов, которое поможет найти ответ на любой вопрос. Во-вторых java создавался с прицелом на высокую надежность: в то время доминировали языки C и C++, которые обладали высоким порогом входа – нужно было внимательно следить за каждой строчкой. Легко можно было выстрелить себе в ногу, неправильно использовав множественное наследование, не почистив правильно память и т.д.
Все это позволило сделать Java в свое время самым надежным языком на рынке, поэтому многие компании выбрали его в качестве основного и не прогадали.
Одной из киллер-фич был подход к исполнению кода. Он не компилировался напрямую в бинарники. Компилятор создавал на основе исходного кода байт-код, который уже в свою очередь с помощью специального приложения – JAVA машины исполнялся на компьютере. Такой подход давал ряд существенных преимуществ: программист мог запустить один и тот же код по разными операционными системами, процессорами вообще без каких-либо изменений!
Второе важное преимущество – с каждой новой версией Java-машина получала новые возможности, которые позволяли оптимизировать код на лету. Один и тот же код с каждой новой версией выполнялся все быстрее и быстрее вообще без вмешательства программистов!
Недавно java перешла на новый релизный цикл – каждая новая версия выходит через полгода. Это позволяет быстрее доставлять новые возможности в язык и практически полностью сократить отставание языка от других jvm языков.
Возможно, сейчас многие из преимуществ непонятны, но изучая java вы поймете, насколько они замечательные и как Java в свое время определила развитие ИТ.
После того как мы скачали все и запустили редактор, приступим к изучению.
Объекты и методы в java
У нас есть автомобиль, у него много характеристик. Мы абстрагируемся от них, нас интересует только два параметра: текущая скорость и максимальная скорость. В мире объектно-ориентированного программирования этот подход так и называется – абстракция. Какое поведение мы ожидаем от автомобиля? Всего два – начать движение и остановиться. Напишем этот код на java в файле с именем Car.java:
Здесь мы описали шаблон будущего объекта – класс. У него есть две переменные состояния – speed и maxSpeed, а также две функции, которые описывают поведение объекта.
Теперь приложение нужно запустить, для этого Java надо подсказать, где находится точка входа в него. Для этого существует особое соглашение – нужно добавить в описание любого объекта метод:
Именно, тот код, который мы в нем укажем и начнет выполняться.
Выполним теперь в консоли следующие команды:
Либо, что в разы проще, просто нажимаем зеленую стрелочку около метода и редакторе:
Наше приложение запустилось, но ничего не произошло, потому что мы не добавили никакой логики в метод. Добавим простой вывод на консоль текста:
Результат выполнения виден на консоли.
Отмечу, что каждый файл может содержать только один публичный класс. То есть ключевое слово public вместе со словом class.
Имя файла должно совпадать с именем класса, включая регистр, и иметь расширение .java. Ключевое слово static, говорит компилятору о том, что данный метод/переменная принадлежит именно шаблону объекта, то есть классу, а не конкретному объекту.
Пока не будем особо на это заострять внимание.
Пакеты в java
Стоит отметить, что часто в программах классы называют одинаково, поэтому была предложена идея их раскладывать по папкам, чтобы не было конфликта имен. Папка, в которой находится класс указывается в самом верху файла:
Создатели предложили называть эти папки как доменные имена в интернете, чтобы точно разграничить их, но это не обязательно, вы можете называть их как захотите.
Это так же позволяет легко импортировать чужие классы в свой код:
В нашем пакете нет определения класса Random, поэтому я воспользовался ключевым словом import, чтобы его добавить в нашу программу. Теперь я могу с ним работать. Используя ключевое слово new я создаю на основе класса объект random, который могу использовать в дальнейшем коде. Далее у объекта я вызываю метод, nextInt который описывает следующее поведение объекта: объект возвращает из метода натуральное число, которое произвольно каким-то образом у себя генерирует. Мы не знаем как именно это происходит – мы знаем только то, что в результате вызова этого метода мы получим какое-то целое число типа int. В объектно ориентированном программировании этот прием называется инкапсуляцией – когда объект внутри себя, основываясь на своем состоянии генерирует некий результат, при этом пользователь данного метода не знает как это работает под капотом.
Продолжим далее разбираться с кодом, который был представлен выше. Начнем с самого простого. Если мы хотим добавить какое-то пояснение к коду, мы можем вставить в любую строчку два косых слеша и весь текст после них будет отброшен при компиляции программы, но при просмотре исходного кода он виден.
Создание объектов
В коде у нас была следующая строка:
Что она делает?
Здесь мы с помощью ключевого слова new создаем новый объект из его шаблона. Мы указываем, что в своем коде мы будем использовать имя random для обращения к этому объекту. В последней строке мы вызываем метод nextInt который приводит к тому, что в нашу программу возвращается какое-то число, после чего это же число мы передаем в метод println который уже выводит его на экран.
Попробуем для создать класс нашего автомобиля и задать ему поведение:
Запустите код и посмотрите, что будет выведено на консоль.
Теперь вернемся к нашим переменным состояния объекта.
Примитивные типы в java
Компьютеры работают в основном с числами, поэтому были придуманы несколько ключевых слов, которые обозначают разные типы данных, по другому их называют примитивные типы данных:
Ключевое слово | Тип | Пример |
boolean | true или false (8-bit число) | true |
byte | 8-bit число | 123 |
short | 16-bit число | 123 |
int | 32-bit число | 123 |
long | 64-bit число | 123L |
float | 32-bit число | 123.0f |
double | 64-bit число | 123.0 |
char | 16-bit число | 'a' |
Так как компьютеры развивались постепенно, то на разных этапах своей жизни они могли хранить число, которое не выше разряда процессора, на котором они работают.
Поэтому появилась такая битовая таблица различных чисел. В нашем случае мы будем использовать числа типа int, которые могут быть описаны 32 битами в памяти компьютера. То есть наши числа будут лежать в промежутке -2,147,483,648 (-2^31) to 2,147,483,647 (2^31-1).
Конструкторы объектов
Теперь мы хотим, чтобы наше поведение зависело и от внутреннего состояния объекта. Для этого его надо как-то задать. Посмотрим как это можно сделать.
Мы добавили в наш код специальный код – конструктор. Он позволяет инициализировать объект перед тем как начать им пользоваться. При этом при создании объекта в методе main я добавил два натуральных числа, которые соответственно инициализировали состояние объекта. В конструкторе мы можем указать любую логику, которую необходимо выполнить при создании объекта.
Если вы запустите приложение, то увидите, что кроме строк на экран выводятся и числа – как раз те, которые мы передали в конструкторе. Теперь наш объект инициализирован – у него есть какое-то внутреннее состояние.
Ссылки vs примитивные типы
Теперь рассмотрим ключевое различие между ссылками на объект, которые мы использовали для работы с объектами и примитивными типами. Для ссылок мы можем написать так:
Мы приравняли наш указатель на объект к ключевому слову null, которое говорит виртуальной машине, что по данному указателю уже нельзя обращаться к объекту, то есть вызов myCar.start(); приведет к ошибке. Что произойдет с нашим объектом, который мы создали? В виртуальной машине java запускается сборщик мусора, который обнаружит, что данный объект живет без какой либо ссылки и удалит его из памяти – то есть сотрет его в оперативной памяти.
Но подобное не работает с примитивными типами:
Подобный код вызовет ошибку.
Также стоит отметить, что строки также являются классами, но при этом для них в языке сделаны существенные изменения.
Строки описываются классом String. Посмотрим какие исключения для них есть:
Это единственный класс, который мы можем создать без оператора new.
Кроме того, строки можно складывать друг с другом или другими примитивными типами, но не вычитать, делить и т.п.:
Добавьте данные строчки в наш код и вы увидите, что все прекрасно работает. Но повторюсь, что подобное исключение сделано только для одного класса – String, потому что строки очень часто используются.
Операторы в java
Раз мы упомянули операторы, давайте посмотрим какие предлагает java.
Унарный оператор – это оператор, для работы которого требуется только один операнд, или переменная, часто выполняют простые операции.
Оператор | Описание | Пример |
! | Инвертирует логическое значение булевой функции | !true будет равно false |
+ либо - | Указывает на знак числа | -123 |
++ | Добавляет к числу единицу | var i = 5; i++; //i будет равно 6 |
-- | Отнимает от числа единицу | var i = 5; i--; //i будет равно 4 |
Далее мы перейдем к операторам, принимающим два аргумента, которые называются бинарными операторами. Бинарные операторы являются самыми распространенными операторами в языке Java. Ранее мы уже познакомились с оператором сложения для строк.
Они могут использоваться для выполнения математических операций над переменными, создания логических выражений и выполнения базовых присваиваний переменных.
Оператор | Описание | Пример |
+ | Cложение | var i = 5; var k = 6; System.out.println(i + k) |
- | Вычитание | var i = 5; var k = 6; System.out.println(i – k) |
* | Умножение | var i = 5; var k = 6; System.out.println(i * k) |
/ | Деление | var i = 5; var k = 6; System.out.println(i / k) |
% | Взятие по модулю | var i = 15; var k = 6; System.out.println(i % k) |
Если мы пишем сложное математическое выражение, то лучше пользоваться скобками:
var x = 5 * (6 – 8);
Завершить секцию с операторами я хотел бы таблицей с условными операторами, результат работы которых true либо false:
Оператор | Описание | Пример |
== | Сравнение на равенство | var i = 5; var k = 6; System.out.println(i == k) |
< либо <= | Меньше, меньше либо равно | var i = 5; var k = 6; System.out.println(i < k) |
> либо >= | Больше, больше либо равно | var i = 5; var k = 6; System.out.println(i >= k) |
&& | Логическое И. В обоих частях должно быть true, чтобы оператор вернул true | System.out.println(true && true) |
|| | Логическое ИЛИ. Хотя бы в одной части должно быть true, чтобы оператор вернул true | System.out.println(false || true) |
Мы познакомились с самыми популярными операторами в java, настало время их использовать.
Мы хотим, чтобы наш автомобиль мог менять свое поведение, а именно изменять текущую скорость, которую мы задали при его создании.
Добавим новый метод, который будет это делать:
Но мы не хотим, чтобы кто-то, кто пользуется нашим классом мог устанавливать скорость выше максимальной.
Условные выражения
Для того, чтобы наложить какие-либо ограничения на переменную, нам необходимо провести проверку, для этого нам понадобиться использовать условное выражение if:
В круглых скобках мы помещаем условие, которое должно вернуть либо true либо false, а в фигурных мы добавляем тот код, который будет выполнен если условие правдиво.
Так же, если условие вернуло false мы можем добавить с помощью ключевого слова else еще один блок кода, который выполняем в таком случае:
Циклы
Что делать, если мы хотим повторять какой-то блок кода много раз? Если вместо if написать while у нас получится самый простой цикл, который будет выполняться до тех пор пока выражение в круглых скобках истинно или не произойдет прерывания цикла с помощью ключевого слова break, либо пока программа не завершиться, например из другого потока. Выглядеть это будет так:
В данном случае, мы создаем некую примитивную переменную i изначально равную 0. В цикле у нас есть условие того, что она меньше 10, если это не так, то код в фигурных скобках выполняться не будет. В них же мы увеличиваем значение i на единицу, если бы мы этого не делали, то выполнялся бы цикл вечно. Запустите программу и посмотрите, что будет выведено на экран и сколько раз.
Для того, чтобы отделить ту логику работы цикла от нашего кода был создан цикл for.
Найдите различия:
В круглых скобках у нас создается счетчик, передается условие цикла и рецепт, что делать со счетчиком на каждой итерации. Согласитесь, так код выглядит более компактно и логика управления циклом не перемешивается с нашей логикой.
если не передать никаких условий, то получится бесконечный цикл:
Выйти из него никак нельзя, только прервав работу приложения с помощью средств операционной системы. Мы можем прервать его работу с помощью ключевого слова break:
Сообщение будет выведено на консоль и на этом месте цикл остановится.
Массивы и коллекции
В своем проекте мы создали только один автомобиль, давайте теперь создадим несколько и поместим их в какое-то хранилище.
В примере выше мы создали два автомобиля, потом создали массив из указателей на объекты класса Car размером 2 и положили в него указатели на наши объекты. Как видно, отсчет ячеек массива начинается с 0. После чего мы использовали специальную модификацию цикла for для массивов и коллекций, который позволяет пройтись по всем элементам и совершить с ними какую-то логику.
То есть теперь у нас есть объект, который может хранить в себе несколько указателей на другие объекты, но работать с массивами не удобно. Нужно знать заранее точный размер и в какие ячейки, что нужно записывать. Поэтому сейчас самый востребованный метод хранения данных – коллекции.
Перепишем наш код с помощью коллекций:
Как вы можете видеть, код не сильно изменился, но пропали эти неуклюжие записи указателей в конкретные ячейки. Теперь все сохраняется автоматически.
Для объявления коллекции мы написали так:
Что это значит? Здесь мы говорим, что будем использовать коллекцию на основе массива и что в нашей коллекции будут лежать объекты типа Car.
Самое важное, что мы не задали размер нашей коллекции. Так как она позволяет добавлять в себя любое число элементов, которые могут поместиться в оперативной памяти вашего компьютера.
Коллекция типа HashSet (множество) не позволит положить в себя два одинаковых значения, сравните выводы:
А потом замените первую строку
Кроме списка так же популярна коллекция Map, она позволяет присваивать объектам ключи и потом получать эти объекты по уникальному ключу:
Я указал строку в качестве ключа – очень частая практика. И положил в коллекцию два своих объекта. Потом я по ключу достал свой объект и вызвал его метод.
Могу сказать, что коллекции – это то, чем вы будете пользоваться всегда в своей работе, поэтому их надо знать. Здесь я рассказал лишь о трех из них, которые используются в 99% случаев, при том не рассказав какие дополнительные методы они несут в себе.
Исключения
Чтобы произошло, если бы я написал свой запрос так:
То на консоли увидел бы следующее:
Возникла исключительная ситуация и программа прервала свою работу. Так как метод get вернул null о котором мы говорили ранее. Соответственно у нас не было объекта на котором мы могли бы вызвать метод. Для предотвращения таких ситуаций был придумал блок try/catch. Посмотрим как он помог бы решить нашу проблему:
После try я в фигурных скобках пишу код, в качестве которого не уверен. В блоке круглых скобок catch я указываю какого типа ошибки могут возникнуть. Ошибки также являются в java объектами. Соответственно в фигурных скобках я указываю ту логику, которая будет выполнена при возникновении исключительной ситуации.
Вывод
Дополнительные материалы:
- Очень рекомендую ознакомится с заметкой ТОП-10 лучших книг по Java для программистов ( https://proglib.io/p/java-books-2019). В ней вы найдете поборку из прекрасных книг по java.
- Если необходимо узнать какую-то информацию о бзовых возможностях java, то лучше официально документации ничего нет – https://docs.oracle.com/en/java/javase/16/
- А если вам нужно усваивать материал через видео, то нет ничего лучше, чем лекции, которые читает Java Champion.
Комментарии