Ключевое слово var

Переменные можно вводить с помощью ключевого слова var, оно позволяет не указывать явно тип переменной:

Вместо

int x = 42;

Можно написать

var x = 42;

Тип переменной определяется по тому, что написано в присваивании (в инициализаторе). В данном случае это будет int потому что справа int.

var s = "abc"; // тип String
var a = new int[]{10, 20, 30}; // тип int[]
var b = {10, 20, 30}; // тип int[]

Хитрость

byte b1 = 42; //тип b1 — это byte (неявное приведение типа)
var b2 = 42; //тип b2 — это int
var b3 = 42L; //тип b3 — это long (из-за L)
var b4 = (byte)42; //тип b4 — это byte

Часто var помогает сократить код, если используется очень длинный тип.

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

Классы, которые мы уже знаем: String, Scanner, PrintStream, File/Path, Pattern — хранит регулярное выражение, Matcher — информация о процессе сопоставления конкретного регулярного-выражения с конкретной строкой.

Создание класса

Договоримся иметь один класс в одном файле. Класс = Файл. В Java это почти обязательно, есть требование что публичные классы (общедоступные классы) обязательно должны называться ровно так же, как называется файл. Т.е. класс TaskA должен быть в файле TaskA.java.

Классы можно раскладывать по папочкам. Например

src (каталог исходников)
     A.java    (public class A)
     B.java    (public class B)
     p1        (каталог = пакет)
         A.java (public class A)
         X.java (public class X)
         p2    (каталог = пакет)
              A.java (public class A)
              Y.java (public class Y)
     p3        (каталог = пакет)
         A.java (public class A)
         Z.java (public class Z)

Про классы A и B из каталога src говорят, что они лежат в «пакете по-умолчанию». Лучше не иметь классов в пакете по-умолчанию и положить их в какие-то другие пакеты.

Каталог p1 задаёт пакет, который называется p1. Каталог p3 задаёт пакет, который называется p3. Каталог p2 задаёт пакет, который называется p1.p2. Имя пакета соответствует пути от корневой директории с исходными файлами, но вместо разделения путей используется точка.

Имена пакетов принято делать маленькими буквами, обычно это одно слово.

Очень часто код пишут в пакетах с длинными названиями, соответствующими домену проекта, но в обратную сторону: arts.spbu.ru (ф-т искусств, допустим), т.е. логично наш код писать в пакете: ru.spbu.arts.progtech.posov.task1.

Полное имя класса — это имя пакета, точка, имя класса. Например, полное имя класса Y — это p1.p2.Y.

Если вы пишете код класса из какого-то пакета (не по-умолчанию), в начале обязательно надо написать package имя.пакета; Имя обязано соответствовать структуре каталогов.

Если вы хотите в одном классе использовать класс из другого пакета, вы должны написать:

// import полное имя класса;
import java.util.Scanner; //Scanner из пакета java.util

// можно писать
import java.util.*; //все классы из соответствующего пакета

Или используйте в коде полное имя класса:

java.util.Scanner in = new java.util.Scanner("read me");

Описание класса

package ru.spbu.arts.posov;

public class Book { // в файле Book.java

    // статические элементы класса
    static int numBooks; // сколько всего книг в библиотеке
    static void addBook() {
        Book.numBooks += 1;
    }

    // элементы класса
    // поля, хранят данные
    String title;
    String author;
    int year;

    // методы, совершают действия
    // [тип результата] [имя функции]([аргументы]) {
    // }
    // this — аналог self в Python, только в отличие от Python его не нужно писать явно
    void print() {
        System.out.println(this.author + ", " + this.title + " (" + this.year + ")");
    }
}

Проверть работу класса можно так:

package ru.spbu.arts.posov;

public class BookTester {
    public static void main(String[] args) {
        // создание объекта: new ИмяКласса(аргументы)
        Book book1 = new Book(); //тип = класс
        book1.title = "Harry Potter";
        book1.author = "Joan Rowling";
        book1.year = 1997;

        //оператор new создает объект
        Book book2 = new Book();
        book2.title = "Alice in Wonderland";
        book2.author = "Lewis Carrol";
        book2.year = 1200;

        //в первом print переменная this = book1
        book1.print();
        //во втором print переменная this = book2
        book2.print();

        //Статические элементы, обращаемся к классу
        Book.numBooks = 0;
        Book.addBook();
        Book.addBook();
        System.out.println("Теперь у нас книг: " + Book.numBooks);
    }
}

Ключевое слово this

Соответствует self в Python. В отличие от Python появляется в методе неявно. Сравните:

class A:
    def printX(self): # можно вместо self использовать любое имя
        print(self.x)

a = A()
a.x = 10
a.printX()  # вызов метода b объекта a, фактически вызывается A.b(a)
# эта строка эквивалентна в Python
A.printX(a)

В java:

public class A {
    int x;
    void printX() {
        System.out.println(this.x);
    }
}

// для вызова
A a = new A(); //нужен оператор new, в отличие от Python
a.x = 10;
a.printX(); // вызывается printX, значение this = a.

Ключевое слово this можно не писать, если это не приводит к неоднозначности. this.a всегда означает поле класса, просто a означает либо локальную переменную, либо поле, если его нет:

void printX() {
    System.out.println(x); // подразумевается поле, других x нет.
}

Я рекомендую не писать this без необходимости.

Конструкторы

Аналог (хотя с уточнениями) метода __init__ в Python. Конструктор в Java и метод __init__ в Python нужны, чтобы инициализировать содержимое объекта перед первым использованием. Напомним пример, мы делали класс рациональных чисел. Rational(4, 8) создавалась дробь с числителем 1 и знаменателем 2. В конструкторе дробь сокращалась. Еще можно в конструкторе проверить, что знаменатель не 0, иначе выдать ошибку.

Важный принцип — объектом можно пользоваться только после того, как у него сработал конструктор. Другими словами, оператор new создает объект и вызывает конструктор.

В Java конструктор оформляется как метод, но у него не пишется возвращаемое значение и имя совпадает с именем класса:

public class Book {
    String title;
    String author;
    int year;
    
    //Распространенный вид конструктора
    //получить значения, присвоить их полям
    //заодно можно проверить или обработать значения
    Book(String title, String author, int year) {
        // второй title (без this) соответствует аргументу.
        this.title = title; 
        this.author = author;
        this.year = year;
    }
}

Теперь при создании объекта книги нужно указывать аргументы:

Book harryPotter = new Book("Harry Potter", "Rowling", 1999);
System.out.println(harryPotter.year); //1999

Перегрузка методов, конструкторов

Java позволяет делать в классе несколько методов с одним именем, аналогично, позволяет делать несколько конструкторов:

class Book {
    // начало как раньше, поля year, title, author
    
    String title;
    String author;
    int year;
    
    // конструктор как раньше
    Book(String title, String author, int year) {
        // второй title (без this) соответствует аргументу.
        this.title = title; 
        this.author = author;
        this.year = year;
    }
    
    Book(String title, int year) {
        this.title = title;
        this.author = "Аноним";
        this.year = year;
    }
    
    void print() {
        //System.out.println("Книга автор название год ..... ");
        print(true, true, true); //см. следующий метод
    }
    
    //печатать ли автора
    void print(boolean printAuthor, boolean printTitle, boolean printYear) {
        //разбор случаев, что печатать, что не печатать
        if (printAuthor)
            System.out.println("Книга автор название год ..... ");
        else
            System.out.println("Книга название год..... ");
    }
}

При использовании книги можно создавать и печатать их по-разному:

Book b1 = new Book("Harry Potter", "Rowling", 1999);
Book b2 = new Book("Java tutorial", 1800);
System.out.println(b1.author): //Rowling
System.out.println(b2.author): //Аноним

b1.print();
b1.print(true, false, true);

Т.е. имена методов одинаковые, но сами методы разные.

Модификаторы доступа

Позволяют указать для каждого элемента класса (поля, методы, конструкторы), из каких мест его можно использовать. Например, поле может быть private int year или public int year.

До этого момента мы не писали модификаторы доступа, кроме public class. Теперь договоримся писать модификаторы всегда. В java их 4:

Перепишем класс Book:

public class Book {
    private String title;
    private String author;
    private int year;

    public Book(String title, String author, int year) {
        this.title = title; 
        this.author = author;
        this.year = year;
    }
}

Конструктор чаще всего бывает public, потому что объект класса создаётся из кода в другом классе.

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

Методы бывают private для вспомогательных действий, и public для тех операций, которые инициируются вне класса.

А пустым модификатором доступа я прошу не пользоваться.

get- и set- методы

Проблема. Если создать книгу, которая описана в прошлом примере, мы не имеем доступа к ее полям:

var b = new Book("Java", "Posov", 2021); // Book b = 
System.out.println(b.title); // title приватный, доступа нет
b.title = "Java 17"; // доступа нет

Иногда это то поведение, которое нужно. Мы не разрешаем пользоваться нашими полями. Вспомним рациональные числа. Было бы хорошо делать так?

var r = new Rational(10, 20); // 1/2
r.numerator = 2; //получится дробь 2/2, несокращенная

Нарушили важное условие данных в классе, что дробь всегда несократима.

Или хорошо ли делать так?

String s = "abc";
s.hashIsZero = true; //нельзя, это поле в String приватно

Принцип Инкапсуляции говорит, что при использовании объекта мы не должны знать детали его реализации. Мы должны знать только его публичные методы, т.е. только те возможности по использованию объекта, которые заложены автором.

В старых Java этого поля не было, в будущих версиях, возможно, оно исчезнет. Принцип инкапсуляции позволяет изменять реализацию класса (объекта), не требуя изменять реализацию того, что его использует.

Как же все-таки разрешить узнавать заголовок книги?

public class Book {

    // это так же, как было
    private String title;
    private String author;
    private int year;

    public Book(String title, String author, int year) {
        this.title = title; 
        this.author = author;
        this.year = year;
    }
    
    //добавляем get- методы, чтобы узнать значение полей
    //public ТИП getИМЯ(БЕЗ АРГУМЕНТОВ)
    public String getTitle() {
        return title; // return this.title;
    }
    
    public String getAuthor() {
        return author;
    }
    
    public int getYear() {
        return year;
    }
    
    //можно делать get- методы для того, что не хранится напрямую
    public String getReferenceItem() {
        return "%s: %s, %d".formatted(author, title, year);
    }
   
    //-set методы устанавливают данные
    // public void setИмя(ТИП ИМЯ)
    public void setTitle(String title) {
        this.title = title;
    }
}

Как пользоваться?

var b = new Book("Java", "Posov", 2021);
System.out.println(b.getTitle()); //Java
System.out.println(b.getYear()); //2021
System.out.println(b.getReferenceItem()); //Posov: Java, 2021

b.setTitle("Java 17"); // можно изменить заголовок
b.setAuthor("..."); // автора не поменять. Нет такого метода.

Перерыв (до 13:43)