Добро пожаловать во вторую часть нашего пути к изучению программирования на Kotlin. Если вы не читали первую часть, смело делайте это здесь.

Если вы новичок, мы решаем первую задачу Advent of Code 2015 года. Я считаю, что это очень простая задача, которую можно использовать для обучения многим вещам в программировании. В предыдущей статье вы узнали много вещей, которые позволили решить задачу и получить правильный ответ. Вы изучили переменные с var, значения с val, условия с if, сравнения на равенство с == , арифметические выражения. с + и -, присваивания с =, строки, циклы for, функция main strong> и print и println.

Я знаю, что это было много вещей, но вы будете использовать все эти вещи снова и снова, поэтому не беспокойтесь об этом, если вы еще не чувствуете себя комфортно. То же самое будет и в этой статье, много понятий, которые вам придется освоить, но вам не нужно ничего запоминать, вам просто нужно понимать понятия. В конечном итоге вы все запомните, потому что используете их все время.

В нашем предыдущем решении у нас было следующее:

и вы заменили эту строку содержимым файла input.txt, указанного в задаче, чтобы получить решение. Теперь вы узнаете, как читать из этого файла напрямую, вместо того, чтобы копировать и вставлять его.

В связи с этим вы больше не сможете писать код для функции Попробовать Kotlin на веб-сайте Kotlin, потому что у вас не будет доступа к файлу. Вам придется написать код в другой программе, но это будет темой другого урока, сегодня вы просто узнаете о задействованных концепциях. Мы будем использовать IntelliJ IDEA Community Edition на тот случай, если вы захотите попробовать сами.

Котлин и Java

Java — это еще один объектно-ориентированный язык программирования, такой как Kotlin. Несмотря на то, что мы используем Kotlin, важно понимать, что между Kotlin и Java существует очень тесная связь. Большая часть функций, которые вы будете использовать в Kotlin, на самом деле исходит из Java. Вам не нужно будет изучать Java, но у вас будет побочный эффект в виде изучения большого количества функций Java по пути.

Объектно-ориентированного программирования

В этом разделе будет представлено то, что обычно задерживается при обучении программированию, и с чем лично я не согласен. В настоящее время это настолько фундаментальная концепция, что я считаю, что ее можно преподавать, не делая ее слишком запутанной.

В предыдущем разделе я упоминал, что и Kotlin, и Java являются объектно-ориентированными языками программирования. Что это значит? Это означает, что большую часть времени вы будете создавать объекты и заставлять их взаимодействовать друг с другом, чтобы получить желаемый результат. Что такое объект? Это может быть что угодно, и к концу этой статьи это станет немного яснее.

Вы уже создали объект в своем предыдущем решении, строка — это объект. Когда вы пишете “()()()”, Kotlin автоматически создает для вас строковый объект. Если строка является объектом, вы можете задаться вопросом, является ли файл также объектом, и было бы правильно предположить это.

Если файл также является объектом, как мы можем его представить? В большинстве объектно-ориентированных языков программирования вы обычно представляете объекты с помощью классов.

Классы и объекты

Лучший способ, который я нашел для объяснения основ классов и объектов, — это подумать о пластиковых игрушках, которые можно наполнить песком, например, в которые некоторые из нас играли на пляже в детстве. Если вы не понимаете, о чем я, вот картинка.

Что дети делают с этими пластиковыми игрушками, так это наполняют их песком, чтобы сделать несколько копий этой пластиковой формы. Используя эту аналогию, пластиковая форма — это класс, а копии из песка называются объектами, также называемыми экземплярами этого класса. В основном класс предоставляет вам форму для объекта. Надеюсь, аналогия вам понятна.

Еще одна очень важная концепция, которую вы должны понимать, заключается в том, что некоторые классы могут иметь некоторое сходство и принадлежать к одной и той же категории. На картинке выше почти все классы (пластмассовые игрушки) — водные животные, а бабочка — насекомое.

Иерархии классов

Концепция иерархии классов имеет решающее значение в объектно-ориентированном программировании так же, как и в жизни, и она вводит другие концепции, которые также очень важны для понимания. Например, на предыдущей картинке вместо того, чтобы называть всех животных, вы можете сказать, что у вас много водных животных. В этом смысле водные животные — это абстрактный класс, вы не знаете точно, о каких животных идет речь, но знаете, что они живут в воде.

Абстрактные классы

Эта концепция абстрактного класса также существует в объектно-ориентированном программировании, и вы узнаете, как ее использовать, в следующей статье.

Продолжая пример с рисунка, и осьминог, и акула являются подклассами класса Водные животные. Это подводит нас к понятию полиморфизма.

Полиморфизм

«Поли» означает «много», а «морф» означает «форма». Это странное название просто означает, что что-то может принимать разные формы, как в случае с нашим водным животным. Если я попрошу у вас бабочку, вы дадите мне эту игрушку, но если я попрошу у вас водное животное, вы можете дать мне акулу, осьминога или любого другого водного животного. Вы также увидите это в действии в следующей статье.

Файл класса

Первый класс, который вы изучите, — это класс File. Как вы понимаете, вы будете использовать его для представления файла. Как мы можем создать объект File? Для создания объектов вам нужно использовать так называемый конструктор. Может быть много разных конструкторов, и вам всегда придется проверять документацию класса, чтобы знать, какой из них использовать. В этих статьях я расскажу вам, какой из них вам нужен, но вы всегда можете проверить документацию самостоятельно, если вам нужно.

Документация

Вам может быть интересно, о какой документации я говорю. Поскольку я знаю, что большая часть функциональности Kotlin исходит из Java, я почти всегда обращаюсь к документации по классам Java. Если щелкнуть эту ссылку для класса Файл, откроется документация, которая называется Javadoc. Там вы найдете много информации, которую вы можете прочитать, но, вероятно, ничего не поймете, поэтому просто прокрутите вниз, пока не увидите раздел Сводка конструктора.

Пусть вас не пугает вся эта документация. Я буду вести вас по пути, чтобы вы могли игнорировать то, чего не понимаете. Вы будете обращаться к документации до конца своей карьеры программиста. Теперь важно, чтобы вы знали, что документация существует, поэтому вам не нужно ничего запоминать. Если вы его забудете, вы просто проверите документацию, как и все остальные. Каждый раз, когда я впервые обращаюсь к классу, я помещаю ссылку на Javadoc, чтобы вы могли щелкнуть по ней и проверить ее, если хотите.

Интересующий нас конструктор — это File(String pathname). В нашем случае путь будет просто «input.txt», и мы создадим наш объект следующим образом: File(«input.txt»). Вы увидите это в действии позже.

Этого достаточно? Не совсем. Теперь нам нужно знать, какой класс позволяет нам читать из этого файлового объекта.

Входной поток

Один из этих классов называется InputStream. В документации этого класса говорится:

Этот абстрактный класс является надклассом всех классов, представляющих входной поток байтов.

Как видите, InputStream — это абстрактный класс. Это означает, что мы не можем создать из него объект, точно так же, как мы не можем создать водное животное, нам нужно конкретное животное, может быть осьминог или акула, но просто водное животное не работает.

Если InputStream является абстрактным классом, вероятно, существует подкласс для удовлетворения наших потребностей. Конечно, есть, и мы могли бы использовать, например, FileInputStream.

FileInputStream

В документации сказано следующее:

FileInputStream получает входные байты из файла в файловой системе. Доступные файлы зависят от среды хоста.

FileInputStream предназначен для чтения потоков необработанных байтов, таких как данные изображения. Для чтения потоков символов рассмотрите возможность использования FileReader.

Давайте разберемся, как мы можем использовать этот класс. Во-первых, какой конструктор мы будем использовать? Не стесняйтесь обращаться к документации и проверять сводку конструктора.

Вы увидите FileInputStream(Файловый файл). Мы могли бы использовать его, поскольку у нас уже есть объект File. Он будет создан следующим образом:

FileInputStream(Файл("ввод"))

Позже я покажу вам разные способы сделать это. Попробуем использовать это сейчас. Как мы читаем из файла? Чтобы использовать функциональность объекта, вам нужно будет просмотреть документацию и понять, есть ли что-то, что соответствует вашим потребностям. Поскольку я учу вас, я сделаю это за вас, но вы можете проверить, что доступно в Сводке методов класса FileInputStream, если вам интересно.

Методы и функции

Я начал использовать Java в 2001 году, и каждая функциональность класса называется методом. Поскольку я использую Java так много лет, я могу использовать слово «метод», даже если мы находимся в контексте Kotlin. В Котлине все называется функцией. Я мог бы написать метод или функцию, но для вашего уровня понимания вы можете предположить, что они все одинаковы. Вы будете видеть, что слово «метод» используется каждый раз, когда я говорю о Java, потому что именно такое название использует Java.

метод чтения()

В этой сводке прочитанного вы видите слово int. Вы можете задаться вопросом, что это такое. Вскоре вы узнаете об этом из этой статьи. Этого достаточно, чтобы вы поняли, как использовать read()? Не совсем. Вы должны нажать на нее и получить полную документацию.

В этих деталях есть много важных вещей, но сейчас нам нужна часть «Возврат».

Возвращает:
следующий байт данных или -1, если достигнут конец файла.

Конец файла

Еще одна концепция, которую вам нужно понять. Да, я знаю. В начале, когда вы изучаете программирование, это может быть довольно болезненно, потому что это слишком много понятий. Но я могу гарантировать вам, что все это станет вашей второй натурой, и вам даже не придется об этом думать. Вы будете использовать все эти вещи столько раз, что они навсегда останутся в вашем мозгу.

Когда вы читаете из файла, в конце концов читать будет нечего. Это означает, что вы достигли конца файла, обычно представленного просто EOF. Теперь, когда вы увидите EOF в литературе, вы поймете, о чем они говорят.

Чтение из файла

Так много концепций, чтобы, наконец, прийти к чтению из файла. Из документации видно, что когда вы вызываете функцию read(), она возвращает следующий байт данных. Вы узнаете больше о байтах в следующей статье. Не беспокойтесь об этом сейчас. Вызов функции – это выполнение любого алгоритма внутри этой функции и получение результата. Поскольку мы получаем только один байт, а нам нужны все байты, как нам получить их все? Нам нужно повторять вызов этой функции до тех пор, пока не останется ничего для чтения. Откуда мы знаем, что больше нечего читать? Вот когда в игру вступает -1, потому что в документации сказано "-1, если достигнут конец файла". По сути, мы хотим вызывать read() до тех пор, пока read не вернет -1.

Эта концепция повторения чего-либо до тех пор, пока не будет выполнено определенное условие, существует в некоторых языках программирования, но не в Котлине. В Kotlin мы должны делать что-то, пока выполняется определенное условие. Если бы у нас была возможность повторить до, мы бы повторяли read() до тех пор, пока не прочитаем == -1. Помните оператор == для проверки на равенство. Поскольку у нас нет такой возможности, нам нужно выполнить read(), пока мы читаем не == -1. Поэтому нам нужно научиться выражать неравенство в Котлине. Это делается оператором !=.

Вы думали, что уже научились читать из файла? Я обманул тебя! Вам придется узнать больше вещей.

делать пока

Наш файл input.txt содержит инструкции для перехода вверх или вниз, и мы хотим читать и реагировать на все инструкции из файла, пока не появится -1.

Вы могли заметить эти красные подчеркивания в коде. Точно так же, как Microsoft Word или Google Docs подчеркивают красным цветом слова, которые неверны согласно определенному словарю, IntelliJ IDEA также делает то же самое, когда находит код, который не имеет смысла. Почему он считает, что это не имеет смысла? Это момент, когда вы должны узнать о концепции типов. Kotlin — это язык, в котором есть так называемый вывод типов. Поэтому в большинстве случаев вам не придется явно объявлять типы. Компилятор Kotlin сделает вывод из вашего кода, что вы имеете в виду. Компилятор — это программа, которая преобразует то, что вы пишете, во что-то, понятное компьютеру. Я напишу о компиляторах в следующей статье, но мы еще далеки от этого, потому что это очень сложная тема.

Если вы помните сводку метода read(), там было слово int. Это означает, что тип того, что вы получаете от вызова read(), представляет собой целочисленное значение, которое в Kotlin представлено классом Int (int в Java). Используя аналогию с блоком для переменных и значений, мы помещаем результат read() в блок с именем инструкция типа Int.

Когда вы пишете символ в одинарных кавычках, например ‘(‘ и ‘)’, с ним также связан тип, это Char в Kotlin (char в Java). Если вы читаете документацию по Java и видите возвращаемый тип char, вы знаете, что его эквивалентом в Kotlin является Char.

Типы: Int и Char

Ошибка, на которую жалуется компилятор,

Оператор «==» не может быть применен к «Int» и «Char»

Причина этого в том, что мы не сравниваем одни и те же типы. Это все равно, что сравнивать яблоки с апельсинами.

Здесь нам нужно преобразовать тип Int в тип Char. Вы поймете, что это значит и как это возможно, в следующей статье, но сейчас вы узнаете, что в классе Int также есть функция, которая позволяет вам преобразовать Int в Char. Это функция toChar(), и мы можем вызвать ее, написав instruction.toChar().

Оператор точки

Это обозначение использования точки также важно понимать. Когда у вас есть объект и вы хотите вызвать функцию, принадлежащую классу этого объекта, вы пишете object.function() точно так же, как мы делали это раньше.

Вместо того, чтобы писать toChar() дважды, мы могли бы сделать это только один раз, сохранить результат в значении и использовать его. Давайте переименуем значение нашей инструкции в input и используем instruction для уже преобразованной, например:

Собираем все вместе

Наконец, давайте все вместе. Мы создаем наш объект File и сохраняем его в значении. Мы создаем объект FileInputStream и передаем ему наш файловый объект. Затем мы повторно используем наш алгоритм и применяем его внутри цикла do-while.

Запуск этого кода даст нам тот же результат, что и код из предыдущей статьи, но теперь мы читаем непосредственно из файла, а не копируем и вставляем строку из файла input.txt.

Как я упоминал в прошлой статье, на этот раз я использовал ++ вместо += 1, просто чтобы показать вам, что вы также можете использовать это, и это довольно распространено. Я лично считаю += 1 более явным и удобным для начинающих.

Так много концепций, чтобы понять всего несколько строк кода, но поверьте мне, может показаться, что сейчас вы работаете очень медленно, но очень скоро вы откроете столько возможностей, что почувствуете себя волшебником. Так же, как и на гитаре, вы должны научиться играть медленно, чтобы иметь возможность играть быстро и, как сказал Майкл Анджело Батио:

Я дам тебе ключи от Ламборгини.

Заключение

Есть еще много важных концепций, которые нужно изучить, но эта статья уже длинная, поэтому я оставлю ее для следующей. Если вы достигли этого момента, поздравляю и дайте мне знать, если вы что-то не поняли. Я предполагаю, что есть еще много вещей, которые вы не поняли, но в следующей статье мы вернемся к этой теме, чтобы ваши знания обо всем этом стали действительно прочными.

Не забудьте похлопать, если вам понравилась эта статья, и подпишитесь на меня, если хотите получать мои следующие статьи. Напишите мне в комментариях, если вы что-то не поняли, чтобы я мог объяснить это в следующей статье.