diff --git a/README.md b/README.md index d6a4a53..3d110d6 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Disclaimer: лекции по этому курсу в норме читаются голосом. Электронный конспект делается по необходимости из-за карантина. Именно этим объясняется его разрозненность. - 1. Java: введение, синтаксис -- TODO. [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java01.pdf) - 1. Разработка классов с данными и контейнеров (Java, задание 1) -- TODO. [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java02.pdf) + 1. [Java: введение, синтаксис](tutorial/01_Hello_World.adoc). [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java01.pdf) + 1. Разработка классов с данными и контейнеров (Java, задание 1). [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java02.pdf) 1. [Разработка классов с данными (Kotlin, задание 1.1)](https://github.com/Kotlin-Polytech/KotlinAsFirst/blob/master/tutorial/chapter11.adoc) 1. [Разработка классов-контейнеров (Kotlin, задание 1.2)](https://github.com/Kotlin-Polytech/KotlinAsFirst/blob/master/tutorial/chapter12.adoc) - 1. Библиотека коллекций Java: итераторы, коллекции, потоки, списки -- TODO. [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java03.pdf) + 1. [Библиотека коллекций Java: итераторы, коллекции, потоки, списки](tutorial/05_Collections_Lists.adoc) -- TODO. [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java03.pdf) 1. Библиотека коллекций Java: множества, ассоциативные массивы -- TODO. [Презентация](http://kspt.icc.spbstu.ru/media/files/2020/java/Java04.pdf) 1. [Консольные приложения (Java/Kotlin, задание 2): командная строка, обработка исключений](tutorial/07_Console_Exceptions.adoc). Дополнительно: [Презентация лекции](http://kspt.icc.spbstu.ru/media/files/2020/java/Java05.pdf). Внимание: презентация содержит только краткую выжимку, рекомендуется прочитать раздел туториала полностью! 1. [Шаблонные классы (Java/Kotlin)](tutorial/08_Generics.adoc) diff --git a/pom.xml b/pom.xml index 8162710..1c850d7 100644 --- a/pom.xml +++ b/pom.xml @@ -9,8 +9,8 @@ 0.0.1-SNAPSHOT - 1.3.11 - 1.8 + 1.4.21 + 14 UTF-8 UTF-8 @@ -28,7 +28,7 @@ junit junit - 4.12 + 4.13.1 test @@ -46,6 +46,11 @@ jdom 1.1 + + org.openjfx + javafx-controls + 15.0.1 + src @@ -73,7 +78,7 @@ org.jetbrains.kotlin ${kotlin.version} - 1.8 + 14 @@ -92,6 +97,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + true + part2.recode.java.RecoderLauncher + + + + org.apache.maven.plugins maven-assembly-plugin @@ -114,6 +131,14 @@ + + org.openjfx + javafx-maven-plugin + 0.0.5 + + part3.simple.hello.javafx.HelloJavafx + + \ No newline at end of file diff --git a/src/part3/simple/components/javafx/Main.kt b/src/part3/simple/components/javafx/Main.kt index 428b732..8d3abeb 100644 --- a/src/part3/simple/components/javafx/Main.kt +++ b/src/part3/simple/components/javafx/Main.kt @@ -13,16 +13,34 @@ class ComponentsView : View("JavaFX components") { minWidth = 300.0 minHeight = 200.0 center { - vbox { - label("Label") + hbox { spacer() - button("Button").setOnAction { - alert(Alert.AlertType.INFORMATION, "", "Button pressed!") + vbox { + spacer() + label("Label") + spacer() + button("Button").setOnAction { + alert(Alert.AlertType.INFORMATION, "", "Button pressed!") + } + spacer() + checkbox("Checkbox") + spacer() + combobox(null, listOf("Combo 1", "Combo 2")) + spacer() } spacer() - checkbox("Checkbox") + vbox { + spacer() + circle(radius = 20.0) { + fill = c(red = 0.5, green = 0.0, blue = 0.0) + } + spacer() + rectangle(width = 20.0, height = 20.0) { + fill = c(red = 0.0, green = 0.0, blue = 0.5) + } + spacer() + } spacer() - combobox(null, listOf("Combo 1", "Combo 2")) } } } diff --git a/test/part2/point/PointTest.java b/test/part2/point/PointTest.java index 59367c8..27f35d5 100644 --- a/test/part2/point/PointTest.java +++ b/test/part2/point/PointTest.java @@ -29,10 +29,10 @@ public void testArrayListMin() { Point result2 = points2.stream() .min(Comparator.comparingDouble(Point::abs)) .get(); - System.out.println("Closest point: " + result + " with distance: " + result.abs()); - System.out.println("Time spent: " + (intermediateTime - startTime)); - System.out.println("Closest point: " + result2 + " with distance: " + result2.abs()); - System.out.println("Time spent: " + (Calendar.getInstance().getTimeInMillis() - intermediateTime)); + System.out.println("[Parallel stream mode] Closest point: " + result + " with distance: " + result.abs()); + System.out.println("[Parallel stream mode] Time spent: " + (intermediateTime - startTime)); + System.out.println("[ Simple stream mode] Closest point: " + result2 + " with distance: " + result2.abs()); + System.out.println("[ Simple stream mode] Time spent: " + (Calendar.getInstance().getTimeInMillis() - intermediateTime)); } @Test @@ -48,9 +48,9 @@ public void testArrayListMapFilterCount() { long result2 = points2.stream() .map(Point::abs).filter(aDouble -> aDouble > 10.0).count(); - System.out.println("Count of points with abs > 10: " + result); - System.out.println("Time spent: " + (intermediateTime - startTime)); - System.out.println("Count of points with abs > 10: " + result2); - System.out.println("Time spent: " + (Calendar.getInstance().getTimeInMillis() - intermediateTime)); + System.out.println("[Parallel stream mode] Count of points with abs > 10: " + result); + System.out.println("[Parallel stream mode] Time spent: " + (intermediateTime - startTime)); + System.out.println("[ Simple stream mode] Count of points with abs > 10: " + result2); + System.out.println("[ Simple stream mode] Time spent: " + (Calendar.getInstance().getTimeInMillis() - intermediateTime)); } } \ No newline at end of file diff --git a/tutorial/01_Hello_World.adoc b/tutorial/01_Hello_World.adoc new file mode 100644 index 0000000..2c7d2d8 --- /dev/null +++ b/tutorial/01_Hello_World.adoc @@ -0,0 +1,416 @@ += Обзор синтаксиса языка Java + +Давайте начнём знакомство с языком Java! Перед вами простейшая программа "Здравствуй, мир". + +[source,java] +---- +// test/Hello.java +package test; +public class Hello { + public static void main(String[] args) { + System.out.println("Здравствуй, мир!); + } +} +---- + +Язык Java отличается некоторой многословностью. +Как видите, даже простая программа на этом языке содержит много всяких слов. +Попробуем в них разобраться. + +== Классы + +Класс является главным элементом Java-программы! +Любая Java-программа всегда содержит хотя бы один класс, а любой код на Java находится внутри какого-то класса. + +С помощью классов реализуется *парадигма программирования* ООП = объектно-ориентированное программирование. +Обучение обычно начинают с процедурного программирования, краеугольным камнем которого +является разбиение задачи на подзадачи, после чего каждая подзадача реализуется как процедура или функция, +а задача в целом -- как главная функция. +В объектно-ориентированном программировании краеугольным камнем являются *понятия* или *объекты*, +которые фигурируют в задаче. +Программист, решая задачу, выделяет в ней наиболее важные понятия и реализует их в виде *класса*. +Класс содержит *данные*, которые описывают понятия задачи, и *функции*, которые работают с этими данными. + +В разделе 2 мы подробно рассмотрим определение понятия через класс, +а пока нам достаточно знать, что в языке Java любой код обязан находиться внутри класса. +Чтобы определить класс, нужно написать код вида +[source,java] +---- +public class Hello { + // Тело класса +} +---- + +Ключевое слово _public_ делает класс *открытым*, то есть доступным во всей программе. +`Hello` является именем класса. + +== Пакеты + +Язык Java придерживается ряда соглашений о структуре проекта. +В частности, все файлы с исходным кодом должны находится в директории, которая называется *Source root* +(переводится на русский примерно как "корневая директория исходного кода"). +В проекте с использованием систем сборки Maven или Gradle директория Source root = `ProjectDir/src/main/java`. +В проекте без использования систем сборки всё проще: Source root = `ProjectDir/src`. +Содержимое же директории *Source root* определяется структорой *пакетов* (_package_). +Например: + +[source,java] +---- +// test/Hello.java +package test; +public class Hello { +} +---- + +Обратите внимание на директиву `package test` перед определением класса. +Это означает, что файл `Hello.java` обязан находиться в директории `test`, которая, +в свою очередь, должна являться поддиректорией Source root. +О классе `Hello` в этом случае говорят, что он находится в пакете `test`, +а *полное его имя* -- test.Hello. +Имя файла обязано совпадать с именем определённого в нём открытого класса +(отсюда, в частности, следует, что открытый класс в файле может быть определён лишь один). + +[source,java] +---- +// another/subpack/Some.java +package another.subpack; +public class Some { +} +---- + +Здесь класс `Some` находится в пакете `another.subpack`, +его полное имя -- another.subpack.Some, он обязан находиться в файле `Some.java`, +а путь к этому файлу должен быть `another/subpack/Some.java`. +Директория `another` должна быть поддиректорией Source root. + +== Тела классов + +На языке Java тела классов пишутся в фигурных скобках. В первую очередь, классы состоят из *полей* (данных) и *методов* (операций над данными). +*Метод* является синонимом *функции* (часто считается, что метод -- это функция, определённая внутри класса). +В классе `Hello` нет полей, и имеется один метод `main`: + +[source,java] +---- + public static void main(String[] args) { + System.out.println("Здравствуй, мир!); + } +---- + +Модификатор _public_ задаёт видимость. Как мы уже видели раньше, _public_ -- это открытая видимость, то есть доступная всем. + +=== Видимости + +Всего в Java имеется четыре разных видимости: + +* открытая _public_ +* закрытая _private_ -- может использоваться только внутри класса, подобный член класса виден только внутри этого класса +* пакетно-закрытая -- не имеет модификатора, видна внутри того же класса, *а также* внутри того же пакета +* защищённая _protected_ -- также может использовать только внутри класса, видна внутри него же, внутри того же пакета, *а также* внутри наследников этого класса (о них поговорим позже) + +Таким образом, видимостей имеется четыре, но модификаторов видимости всего три. Без модификатора (по умолчанию) считается, что видимость пакетно-закрытая (package private). Иногда программисты на Java подобную видимость подчёркивают комментарием: +[source,java] +---- +// FILE: SomeClass.java +package test; + +public class SomeClass { + private int x = 3; + int y = 4; + + public void foo() { + System.out.println(x); // Ok (same class) + } +} + +/* package-private */ class AnotherClass { + public void bar() { + SomeClass sc = new SomeClass(); // Ok (public) + System.out.println(sc.x); // ERROR! (private, access from another class) + System.out.println(sc.y); // Ok (package private, same package) + } +} +---- + +Обратите внимание, что имя `SomeClass` обязано совпадать с именем файла (открытый класс), а имя `AnotherClass` -- нет. + +Наиболее часто программисты используют закрытую видимость (для описания деталей реализации, которые не должны быть видны снаружи) и открытую видимость (для описания доступных всем действий с объектом). Если вы новичок в Java, неплохое правило на первое время -- делать закрытыми все поля и открытыми все методы (после этого можно закрыть все те методы, которые используются только внутри класса). + +Стоит также отметить, что для классов, определённых на верхнем уровне файла, доступными являются только две видимости: открытая _public_ и пакетно-закрытая. Для методов и полей классов, а также для вложенных классов (определённых внутри других классов), доступны все четыре видимости: открытая _public_, закрытая _private_, защищённая _protected_ и пакетно-закрытая. + +=== Статичность + +[source,java] +---- + public static void main(String[] args) { + System.out.println("Здравствуй, мир!); + } +---- + +Как мы видим, функция `main` также является _static_, то есть статической. Чтобы понять, что это такое, нам придётся коснуться разницы между классами (class) и их *экземплярами* (class instance). Иногда вместо "экземпляр класса" говорят "объект класса", это синонимы. + +Статические поля и методы являются общими для всего класса. Для обращения к таким полям и для вызова таких методов экземпляр класса не требуется. + +Нестатические поля и методы специфичны для экземпляра класса. Для обращения к таким полям и для вызова таких методов у вас должен быть экземпляр класса. Нестатический метод может прочитать нестатическое поле (потому что экземпляр класса у него уже есть) или вызвать нестатический метод -- по той же причине. Статический метод, однако, экземпляра класса не имеет и поэтому не может читать нестатические поля и вызывать нестатические методы без явного указания экземпляра класса. + +Попробуйте сами определить, может ли нестатический метод прочитать статическое поле. + +_Примерно то же самое можно объяснить и другими словами. Любой *нестатический* метод имеет дополнительный параметр, не указанный явно в списке -- так называемый *получатель* (receiver). Получатель -- всегда ссылка на экземпляр класса, в котором описан данный метод; для её обозначения можно использовать ключевое слово `this`. *Статический* метод такого дополнительного параметра не имеет. Для вызова *нестатического* метода или обращения к *нестатическому* полю всегда требуется получатель правильного типа, указанный явго или неявно. Для вызова *статического* метода или обращения к *статическому* полю этого не требуется._ + +[source,java] +---- +public class SomeClass { + public int x = 1; + static public final y = 2; + public void foo() { + bar(); // Ok (implicit receiver) + this.bar(); // Also Ok (explicit receiver) + System.out.println(this.x); // Ok (explicit receiver) + System.out.println(y); // Ok (no receiver required) + } + + public void bar() { + baz(); // Ok (no receiver required) + } + + static public void baz() { + System.out.println(y); // Ok (no receiver required) + System.out.println(x); // ERROR (receiver required!) + SomeClass sc = new SomeClass(); + System.out.println(sc.x); // Ok (explicit receiver) + System.out.println("123".x); // ERROR (incorrect explicit receiver) + } +} +---- + +=== Типы + +Язык Java имеет статическую типизацию. Это значит, что тип любой переменной, параметра, поля, результата функции известен на момент компиляции программы либо выводится во время компиляции программы. Типы бывают разные и делятся на две большие группы: + +* *Примитивных* типов всего восемь: четыре целочисленных `int`, `long`, `short`, `byte`; два с плавающей точкой `double` и `float`; логический `boolean`; символьный `char`. Имена примитивных типов записываются со строчной буквы, все они являются ключевыми словами Java (то есть такие же имена нельзя, например, давать переменным). К этой же группе можно условно отнести псевдо-тип `void`, который обозначает отсутствие какого-либо типа. Тип результата функции записывается перед её именем, для функции `main` это как раз `void`, то есть результат у функции `main` отсутствует. +* *Ссылочных* типов может быть неограниченное количество. Их принципиальное отличие от примитивных состоит в том, что в *стеке* для подобных переменных хранится не значение, а ссылка на участок *кучи*, где уже хранится сам объект. Ссылочные типы могут быть описаны классом, или являться массивом (который в свою очередь может хранить примитивные или ссылочные элементы). В функции `main` тип параметра `args` задан как `String[]` -- обратите внимание, что тип здесь тоже находится перед именем, это общее правило для Java. `String` -- это строковый тип, определяемый библиотечным классом `String`. `String[]` -- это массив строк. + +=== Главная функция + +По правилам языка Java, исполнение программы начинается с *главной функции*. Подобная функция обязана называться `main`, иметь открытую видимость, быть статической, иметь массив строк в качестве единственного параметра (через него передаются аргументы командной строки, подробнее см. https://github.com/Kotlin-Polytech/FromKotlinToJava/blob/master/tutorial/07_Console_Exceptions.adoc[раздел 7]) и не иметь результата (тип `void`). Разрешается иметь в одной программе несколько главных функций -- в этом случае при работе из IDE мы сами выбираем, с какой из них начинать работу, а при сборке JAR-пакета это указывается в так называемом MANIFEST-файле. + +Функция в нашем примере удовлетворяет всем этим требованиям и, значит, является главной. С неё начнётся выполнение нашей маленькой программы. + +=== Вывод на консоль + +Как можно догадаться из примера, вывод информации на консоль в программе на Java производится с помощью функции `System.out.println()`. Почему у неё такое длинное название? По правилам Java каждая функция обязана находиться в классе; функция `println` находится в классе `PrintStream`, то есть поток печати. Класс `System` содержит ссылки на два стандартных потока печати -- один для вывода обычной информации, статическое поле `out` и другой для вывода ошибок, статическое поле `err`. Запись `System.out` позволяет нам обратиться к статическому полю класса, а дальнейшее `.println` -- вызвать на соответствующем объекте функцию `println`. + +== Справочник по синтаксису Java + +=== Примитивные типы + +* `byte` (1 байт, от -128 до 127) +* `short` (2 байта, от -32768 до 32767) +* `int` (4 байта, от -2^31 до 2^31-1) +* `long` (8 байт, от -2^63 до 2^63-1) +* `float` (4 байта: 24 бита мантисса + 8 бит порядок) +* `double` (8 байт: 53 бита мантисса + 11 бит порядок) +* `boolean` (1 байт: истина или ложь) +* `char` (2 байта: юникод) + +=== Переменные и поля + +Как мы уже видели в примерах выше, для описания данных (переменных, параметров, констант, полей) Java использует синтаксис с типом впереди (как в языке Си). Для локальных переменных начиная с версии 10 разрешается используется синтаксис `var <имя> = ...`, позволяющий вывести тип переменной автоматически. Например + +[source,java] +---- +public class SomeClass { + public String name = "Some"; // Поле типа String + public int x = 2; // Поле типа int + public void foo(double param /* параметр типа double */) { + char ch = ' '; // Локальная переменная типа char + var y = 3 * 5; // Локальная переменная типа double -- используется вывод типов + } +} +---- + +=== Константы + +Целые + +* `57`, `+323`, `-48` (десятичная форма, 4 байта) +* `024`, `-0634`, `0777` (восьмеричная форма) +* `0xabcd`, `-0x19f` (шестнадцатеричная форма) +* `0b010001001` (двоичная форма, только JDK 1.7+) +* `43_934` (форма с _, только в JDK 1.7+) +* `1234567890123L`, `0xabcdef1234L` (8-байтные, `long`) + +Вещественные + +* `37.29`, `-19.41` (обычная форма, 8 байт) +* `3e+12`, `-1.1e-7` (экспоненциальная форма) +* `3.6F`, `-1.0e-1F` (4-байтные, `float`) + +Символьные + +* `'a'`, `'?'`, `' '`, `'\n'`, `'\t'`, `'\\'` (обычный +вариант) +* `'\40'`, `'\62'` – символ по восьмеричному коду +* `'\u0053'` – символ по юникоду + +Строковые + +* `"Hello, world\n"` +* `"Сложение " + "строк"` + +=== Операции + +* Арифметические: `+` `-` `*` `/` `%`. Сложение-вычитание-умножение-деление-взятие остатка. +* Инкремент/декремент: `++` `--` (увеличение/уменьшение на 1). +* Логические: `&` `&&` `|` `||` `^` `!`. Все логические операции требуют `boolean` аргументов. `&`, `|`, `^` являются жадными; `&&` и `||` ленивыми. +* Сравнения: `>` `<` `>=` `<=` `==` `!=`. Сравнение на равенство для примитивных типов происходит по значению, для ссылочных -- по ссылке. Для сравнения объектов по значению существует функция `equals`. +* Побитовые: `~` `&` `|` `^`. Работают с целочисленными аргументами. +* Сдвиговые: `<<` `>>` `>>>`. Операция `>>` осуществляет арифметический сдвиг, то есть оставляет знак тем же; операция `>>>` осуществляет беззнаковый сдвиг. +* Присваивания/модификации: `=` `+=` `-=` `*=` `/=` `%=` `&=` `|=` `^=` `<<=` `>>=` `>>>=`. Пример: `a += b` эквивалентно `a = a + b`. +* Условная: `a > b ? a : b`. Если условие перед вопросом верно, результат операции -- аргумент перед двоеточием, если нет -- после двоеточия. +* Приведения типа: `int a = (int)2.5`. "Силой" изменяет тип выражения в правой части. Численные типы при этом приводятся друг к другу (выполняется округление, если это требуется). + +=== Ветвления + +Основной оператор ветвления `if (condition) { ... } else { ... }`. Условие должно быть логическим. В ветвях может быть любое количество операторов; если ветвь содержит лишь один оператор, фигурные скобки можно опустить (делать этого не рекомендуется). Оператор ветвления не имеет результата, т.е. код вида `int x = if (a > b) a else b` запрещён, вместо этого можно применять условную операцию `int x = a > b ? a : b`. + +Табличное ветвление по ключу + +[source,java] +---- +switch (someInt /* ключ */) { +case 1: + ... + break; +case 5: + ... + break; +default: + ... + break; +} +---- + +работает так. Если `someInt` в примере равно 1, код выполняется начиная с метки `case 1`. При выполнении оператора `break` мы покидаем конструкцию `switch`. Аналогично, если `someInt` равно 5, выполняем код начиная с метки `case 5`. Если ни одна из меток не содержит истинного значения -- выполняем код с метки `default`. + +По правилам Java, ключом оператора `switch` может являться + +* целое число +* символ +* элемент перечисления +* строка (начиная с версии 1.7) + +Начиная с версии 14, Java разрешает использование *switch expressions* (выражений табличного ветвления), то есть оператор `switch` теперь может иметь результат (который может быть присвоен переменной или использован каким-либо иным образом). Например: + +[source,java] +---- +int grade = switch (gradeWord /* ключ */) { +case "уд", "удовл", "удовлетворительно": + yield 3; +case "хор", "хорошо": + yield 4; +case "отл", "отлично": + yield 5; +case "неуд", "неудовл", "неудовлетворительно": + yield 2; +default: + yield 0; +} +---- + +Выполнение команды `yield` здесь ведёт к формированию результата `switch` и немедленному выходу из конструкции. Можно считать, что `yield` ~= *return from switch*. + +Тот же код может быть записан без помощи `yield`, если использовать новый синтаксис `switch` с заменой `:` на `->`: + +[source,java] +---- +int grade = switch (gradeWord /* ключ */) { + case "уд", "удовл", "удовлетворительно" -> 3 + case "хор", "хорошо" -> 4 + case "отл", "отлично" -> 5 + case "неуд", "неудовл", "неудовлетворительно" -> 2 + default -> 0; +} +---- + +=== Циклы + +Язык Java включает четыре вида циклов: while (с предусловием), do-while (с постусловием), for (со счётчиком), for[each] (для каждого). + +[source,java] +---- +public class SomeClass { + public void foo() { + // 1. Проверить условие, если оно верно, выполнить тело, если нет, выйти из цикла + // 2. Вернуться к пункту 1 + while (condition) { + doSomething(); // Тело + } + + // 1. Выполнить тело. + // 2. Проверить условие, если оно верно, вернуться к пункту 1, если нет, выйти из цикла + do { + doSomething(); // Тело + } while (condition); + + // 1. Выполнить начало (i=0) + // 2. Проверить условие (i<10), если оно верно, выполнить тело, если нет, выйти из цикла + // 3. Выполнить шаг + // 4. Вернуться к пункту 2 + for (int i=0 /* начало */; i<10 /* условие */; i++ /* шаг */) { + doSomething(); // Тело + } + + int[] arr = new int[] { 2, 3, 5, 8, 13 }; // Создать массив из пяти элементов + // 1-5. Для каждого из пяти элементов массива вызвать doSomething() + for (int element: arr) { + doSomething(); // Тело + } + } +} +---- + +Цикл на Java немедленно прерывается, если в его теле выполняется оператор `break`. Другой оператор управления `continue` заставляет цикл немедленно перейти к проверке условия выполнения следующей итерации (другими словами, `continue` немедленно прерывает текущую итерацию цикла). Напомним, что итерацией цикла называется одно выполнение его тела. + +=== Строки + +Строки в Java описываются библиотечным классом `String`. Строковые константы записываются в двойных кавычках. По правилам Java строки можно складывать с помощью оператора `+` и сравнивать на равенство с помощью метода `equals`. Использовать для строк оператор ссылочного равенства `==` не рекомендуется. + +[source,java] +---- +public class SomeClass { + public void foo(String s1) { + String s2 = "Alpha"; + System.out.println(s1.equals(s2)); // true, если s1 тоже "Alpha" + System.out.println(s1 == s2); // true, если s1 и s2 являются ссылками на один и тот же объект класса String + String s3 = "Alpha"; // По факту s2 и s3 ссылаются на один и тот же объект + System.out.println(s2 == s3); // true + String s4 = "Al" + "pha"; + System.out.println(s3 == s4); // Все ещё true + } + + public void bar() { + foo("Alpha"); // Выведется четыре раза true + } +} +---- + +Компилятор Java умеет оптимизировать строковые константы. Если, например, у вас в программе 20 раз встречается константа `"Alpha"`, в памяти будет создан всего один строковый объект с таким содержимым. И даже если вы напишите что-то вроде `String s = "Al" + "pha";`, ваша строка всё ещё равняется `"Alpha"` и переиспользует тот же самый объект. Если, однако, строка составляется из нескольких других и компилятор не может определить, что их значения всегда одинаковы, объекты будут созданы заново. Попробуйте в качестве упражнения написать пример, в котором ссылочное равенство даёт результат `false`, несмотря на то, что строки равны по значению. + +=== Массивы + +Массивы в Java -- единственный составной тип, существующий на уровне языка и не имеющий библиотечного описания (скажем, связанные списки `LinkedList` и строки `String` описаны в Java как классы стандартной библиотеки). Любой массив -- ссылочный тип. Массив элементов типа `Type` обозначается как `Type[]`. Размер (длина) массива всегда задаётся при его создании и в дальнейшем не меняется. Индексы элементов начинаются от нуля, индекс последнего элемента равен размеру массива минус 1. Примеры: + +[source,java] +---- +public class SomeClass { + public void foo() { + double[] darr = new double[10]; // Создать массив из 10 вещественных элементов, содержащий нули + String[] sarr = new String[] { "Alpha", "Beta", "Omega" }; // Создать массив из трёх заданных строк + int[] arr = null; // Создать нулевую ссылку на массив + System.out.println(sarr[1]); // "Beta". Индексация идёт с нуля + System.out.println(darr[10]); // Ошибка во время выполнения -- ArrayIndexOutOfBoundsException. Допустимы индексы от 0 до 9 + System.out.println(arr[0]); // Ошибка во время выполнения -- NullPointerException. Попытка обратиться к массиву по нулевой ссылке + System.out.println(darr.length); // 10 -- число элементов (размер, длина) массива + } +} +---- diff --git a/tutorial/05_Collections_Lists.adoc b/tutorial/05_Collections_Lists.adoc new file mode 100644 index 0000000..f015dd0 --- /dev/null +++ b/tutorial/05_Collections_Lists.adoc @@ -0,0 +1,146 @@ += Коллекции, итераторы, списки + +Здесь мы начинаем рассматривать библиотеку коллекций Java. + +== Основные понятия + +Библиотека коллекций строится на трёх видах кирпичей: + +* *интерфейсы* `interface` определяют, что именно умеет делать тот или иной объект, но обычно ничего не говорят о том, как он это делает +* *абстрактные классы* `abstract class` содержат так называемую частичную реализацию объекта; в библиотеке коллекций они, как правило, реализуют все необходимые методы через несколько базовых +* *классы* `class` отвечают на вопрос о том, какие именно данные хранит объект и как именно он осуществляет те или иные операции + +В прикладном коде интерфейсы обычно используются для определения типов переменных, параметров, полей, результатов методов; классы -- для создания объектов при вызове их конструкторов. Абстрактные классы в прикладном коде используются сравнительно редко, они позволяют за короткое время написать свою реализацию коллекции, если это потребовалось. + +Для описания отношений вида *подтип*, *подвид* используется концепция *наследования*. В объектно-ориентированном программировании ситуация, когда класс (интерфейс) Б является *наследником* класса (интерфейса) А, означает, что: + +* по смыслу объект типа Б является разновидностью объекта типа А +* объект типа Б можно употреблять везде, где нужен тип А +* объект типа Б содержит все данные, которые есть в объекте типа А (и, возможно, какие-либо другие) +* на объекте типа Б можно вызывать все методы, которые можно вызывать на объекте типа А (и, возможно, какие-либо другие) + +Сокращённо говорят, что "public inheritance means IS A", или "открытое наследование означает ЭТО ЕСТЬ". В Java любое наследование считается открытым (в отличие от, например, {cpp}). Кроме этого, с наследованием связано ещё два специальных слова: + +* про класс Б можно сказать, что он *расширяет* класс А или *реализует* интерфейс А (в обоих случаях он является наследником А) +* про интерфейс Б можно сказать, что он *расширяет* интерфейс А (и опять-таки это синоним слова "наследник") +* интерфейс Б никогда не может являться наследником класса А (запрещено правилами Java). + +== Иерархия коллекций + +Коллекции Java образуют иерархию наследования. С ней можно ознакомиться, например, здесь: https://en.wikipedia.org/wiki/Java_collections_framework. Подобное изображение иерархий распространено среди программистов; в них наследник всегда рисуется ниже своего базового класса (интерфейса) и соединяется с ним стрелкой, ведущей в сторону базового класса (интерфейса). + +Вершиной иерархии коллекций, а следовательно, самым общим типом, является интерфейс `Iterable`. Его свойства наследуют все коллекции библиотеки. Благодаря этому интерфейсу нам доступен в Java код следующего вида + +[source,java] +---- + public static void foo(Iterable container) { + for (String element: container) { + bar(element); + } + } +---- + +На месте `Iterable` здесь может стоять `Collection`, `List`, `Set`, `Queue` и цикл for-each всё равно будет для них доступен. Интерфейс `Iterable` тесно связан с интерфейсом-помощником `Iterator` (так говорят в ситуации, когда помощник является бессмысленным без основного интерфейса, и наоборот). Его формальное описание выглядит так: + +[source,java] +---- +public interface Iterable { + Iterator iterator(); // то есть Iterable даёт возможность получить Iterator +} +---- + +`Iterator` является чем-то вроде указателя, путешествующего по коллекции. В момент создания он "смотрит" перед нулевым элементом коллекции. Итератор содержит три метода: + +[source,java] +---- +public interface Iterator { + boolean hasNext(); // Возвращает true, если за итератором есть ещё элементы + E next(); // Возвращает следующий элемент за итератором И передвигает итератор за него + void remove(); // Удаляет из коллекции элемент, который вернул последний вызов next +} +---- + +Порядок ручного использования итератора в общем случае выглядит так: + +1. Создать итератор с помощью вызова `iterator()`. +2. Проверить, есть ли следующий элемент `hasNext()`. Если его нет -- перебор закончен. +3. Достать следующий элемент `next()`. +4. Сделать необходимую его обработку. Если для данного элемента это требуется, его можно удалить вызовом метода `remove()`. +5. Вернуться к пункту 2. + +Всё то же самое можно выполнить с помощью обычного цикла `for-each`, кроме удаления элемента. + +[source,java] +---- + public static void foo(Iterable container) { + for (String element: container) { + bar(element); + } + // Is equivalent to + Iterator it = container.iterator(); + while (it.hasNext()) { + String element = it.next(); + bar(element); + } + } +---- + +Важно, однако, отметить, что удалить элемент в цикле for-each невозможно. Например: + +[source,java] +---- +public class SomeClass { + private static boolean condition(String s) { + return ...// Some condition + } + + public static void foo(Collection container) { + Iterator it = container.iterator(); + while (it.hasNext()) { + String element = it.next(); + if (condition(element)) it.remove(); + } + // Is NOT equivalent to + for (String element: container) { + if (condition(element)) { + container.remove(element); // Produces ConcurrentModificationException + } + } + // However, it's possible... + container.removeIf(SomeClass::condition); + } +} +---- + +Что такое `ConcurrentModificationException`? Это исключение, связанное с так называемым контрактом итератора. Согласно этому контракту, во время работы итератора (от момента, когда он был создан, и до момента, когда на нём был вызван последний метод), запрещается менять коллекцию любым способом, за исключением вызова `remove` на итераторе. + +Про метод `removeIf` см. раздел "Потоки и функции высшего порядка". + +=== Интерфейс Collection и его реализации + +По смыслу интерфейс `Collection` описывает объект, содержащий некоторое количество однотипных объектов. По контракту коллекции, туда можно добавлять элементы и удалять их, а также перебирать их с помощью итератора. Коллекция "как есть" (т.е. без расширений) не нумерует свои элементы, и не запрещает добавлять в коллекцию равные элементы. + +Содержимое интерфейса описано здесь: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html. Повторять это описание вряд ли имеет смысл; здесь мы лишь подчеркнём, что отдельного внимания заслуживают методы `stream()`, `parallelStream()`, `spliterator()` и `removeIf(predicate)`. Все они относятся к поддержке в Java функций высшего порядка, появившейся в версии 1.8 языка. Про неё см. раздел "Потоки и функции высшего порядка". + +Коллекция "как есть" не имеет полных реализаций. Существует, однако, абстрактный класс `AbstractCollection`, обеспечивающий так называемый "скелет" реализации. Расширив этот абстрактный класс с помощью `extends`, мы можем создать собственную реализацию коллекции, добавив туда нужные для хранения данных поля и всего три метода: + +* `iterator()` и `size()`, если мы хотим создать неизменяемую коллекцию. Итератор при этом должен поддерживать `next()` и `hasNext()`. +* `add()`, если мы хотим создать изменяемую коллекцию. Следует добавить также реализацию `remove()` в итераторе. + +Некоторые методы -- в первую очередь `clear()` -- абстрактный класс `AbstractCollection` реализует заведомо неэффективно. + +=== Списки: интерфейс List и его реализации + +Список `List` расширяет коллекцию `Collection`. В отличие от коллекции "вообще", список является пронумерованным -- у каждого элемента есть свой номер (индекс), а по индексу можно достать или изменить элемент `get`, `set`. Индексы нумеруются от нуля до числа элементов `size()` минус один. Список, по-видимому, самая используемая структура данных в языках программирования. + +Также список добавляет ряд методов, связанных с работой с индексами -- например, вставка в список элемента по заданному индексу `add(index, element)` (данная операция "раздвигает" список в этом месте и вставляет туда новый элемент), или, наоборот, удаление по заданному индексу `remove(index)` (здесь список наоборот "схлопывается" в данном месте). Полный список методов можно посмотреть здесь: https://docs.oracle.com/javase/8/docs/api/java/util/List.html. У некоторых методов меняются контракты: + +* Метод `add(element)` в списке всегда вставляет элемент именно в его конец +* Итератор `iterator()` перебирает список по возрастанию индексов +* Сравнение на равенство `equals()` возвращает `true`, если размеры списков равны, и равны все пары элементов с одинаковыми индексами. При сравнении списка с не-списком всегда возвращается `false`. + +К списку функций высшего порядка добавляются `replaceAll(unaryOperator)` и `sort(comparator)`. + +=== Потоки и функции высшего порядка === + +TODO diff --git a/tutorial/10_JavaFX.adoc b/tutorial/10_JavaFX.adoc index b006f03..2de237b 100644 --- a/tutorial/10_JavaFX.adoc +++ b/tutorial/10_JavaFX.adoc @@ -6,7 +6,7 @@ == Поддержка на Java и Kotlin -Библиотека JavaFX входит в состав JDK 1.8, 9 и 10, поэтому для этих JDK программы на Java и Kotlin могут, в принципе, использовать её без подключения дополнительных зависимостей. При использовании JDK 11 и более поздней необходимо явное подключение JavaFX в виде https://gluonhq.com/products/javafx/[загруженного пакета], https://openjfx.io/openjfx-docs/#maven[Maven-зависимости] или https://openjfx.io/openjfx-docs/#gradle[Gradle-зависимости]. Данный проект пока использует JDK 1.8, поэтому JavaFX-зависимость доступна ему непосредственно; загрузить JDK 1.8 можно https://www.oracle.com/java/technologies/javase-jdk8-downloads.html[отсюда]. +Библиотека JavaFX входит в состав JDK 1.8, 9 и 10, поэтому для этих JDK программы на Java и Kotlin могут, в принципе, использовать её без подключения дополнительных зависимостей; загрузить JDK 1.8 можно https://www.oracle.com/java/technologies/javase-jdk8-downloads.html[отсюда]. При использовании JDK 11 и более поздней необходимо явное подключение JavaFX в виде https://gluonhq.com/products/javafx/[загруженного пакета], https://openjfx.io/openjfx-docs/#maven[Maven-зависимости] или https://openjfx.io/openjfx-docs/#gradle[Gradle-зависимости]. Данный проект использует JDK 14 и использует библиотеку javafx версии 15.0.1 (см. файл pom.xml). Для программ на Kotlin существует удобный DSL (Domain Specific Language = предметно-ориентированный язык) https://tornadofx.io[tornadofx] -- см. также https://github.com/edvin/tornadofx[его GitHub-репозиторий]. Этот DSL может быть подключен к программам на Kotlin как дополнительная библиотека -- подробности о подключении дополнительных библиотек см. в https://github.com/Kotlin-Polytech/FromKotlinToJava/tree/master/tutorial/07_Console_Exceptions.adoc[7-м разделе]; опять-таки можно использовать Maven- или Gradle-зависомость, примеры есть https://github.com/edvin/tornadofx[на GitHub в README].