Skip to content

Latest commit

 

History

History
361 lines (278 loc) · 13.2 KB

230210.md

File metadata and controls

361 lines (278 loc) · 13.2 KB

Непримитивные типы

String

  1. Последовательность символов произвольной длины (не класс)
  2. Не null-terminated
  3. Строки не изменяемы
  4. Все строковые литералы кэшируются (можно, потому что строки не изменяются)

Создание строк

String hello = "Hello"
String str = new String(...)

Доступ к содержимому

  1. int length()
  2. char charAt()
  3. char[] toCharArray()

Утечка памяти

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

Всякое

  1. Оператор == для String сравнивает ссылки, а не содержимое строки (в общем-то, как и для любых ссылочных типов)
  2. equals принимает Object, поэтому в теории можно сравнивать апельсин и крокодила.
  3. Есть регулярные выражения
// Регулярки

String str = "a, b, c, d, e"
String items[] = str.split(", *");
// items[] -> {"a", "b", "c", "d", "e"}

Специфика строк

Есть ОДИН перегруженный оператор (это исключение), оператор +. Это конкатенация строк.

StringBuilder

String str = ""
for (...) {
    str = str + " ";
}

// Это чудо работает за квадрат, потому что нужно каждый раз копировать строку и выделять память

Альтернатива - StringBuilder. Это просто список, куда мы складываем строки, а потом в конце просто делаем toString()

StringBuilder buf = new StringBuilder();
buf.append("Hello");
buf.append("World");
buf.append("!");
String result = buf.toString();

Не стоит использовать String concat(String str), всегда пользуемся оператором +. Потому что + - это просто синтаксический сахар над StringBilder.

То есть в случае a + b создаётся StringBuilder, туда складываются a и b, а потом делается toString(). Это становится разумно, если у нас есть цепочка a + b + c + ....

Классы и ООП

Объявление класса

/*modifiers*/ class Example {
    /* class contents : fields and contents */
}

class Example {
    /* modifiers */ int number;
    /* modifiers */ String text = "hello"; 
}

class Example {
    /* modifiers */ Example(int number) { // constructor
        ...
    }
}

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

  1. Всё инициализируется либо явно, либо по умолчанию
  2. Возможна перегрузка методов
  3. Если не объявлен ни один конструктор, автоматически создаётся конструктор по умолчанию без параметров
  4. Декструкторов нет(сборка мусора автоматически)
  5. Есть метод void finilize() - он будет вызван при удалении объекта. Но вызывается он ровно ОДИН раз (иначе можно было бы создать вечный объект). Возможно сборщик мусора не приходит никогда.
  6. Любой код, который написан в finalize() - может выполнится, а может и нет, поэтому при компиляции прилетает warning

Создание экземпляра

Example e = null;
// e.getNumber() -> NullPointerException

e = new Example(3);
// e.getNumber() -> 3

Наследование

class Derived extends Example {
    /* derived class contents */

    Derived() {
        this(10); // вызываем другой конструктор.
    }

    Derived(int number) {
        super(number); // Вызов родительского конструктора.
    }
}
// Если в родительском класс есть конструктор по умолчанию - он
// будет вызван. Иначе нужно вызывать самому
  1. Нет множественного наследования

Интерфейсы

  1. Аналог в плюсах - все методы в классе чистые виртуальные.
  2. Класс может реализовывать несколько интерфейсов.
  3. Если не реализовать в наследнике интерфейса какой-то метод, то класс будет абстрактным.
  4. В интерфейсах нет конструктора
  5. Можно заводить статические константы, которые будут иметь модификаторы public static final (их нельзя изменить)
  6. Можно создавать private методы (начиная с Java 9)
  7. Можно создавать static methods вместе с телом метода
  8. Можно задавать имплементацию по умолчанию, добавив моификатор default (начиная с Java 8)
interface ExampleInterface {
    int getNumber();
}

abstract class Example {...} // нельзя создать экземпляр класса

// Отличия:
// 1. Нет полей (ну точнее это константы)
// 2. Нет конструктора
// 3. Есть множественное наследование

Модификаторы

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

  1. public - доступ для всех
  2. protected - доступ в пределах пакета и дочерних классов
  3. private - доступ в пределах класса
  4. Доступ по умолчанию - доступ в пределах пакета (не рекомендуется)

Ещё есть такие модификаторы

  1. final
    • Можно писать у полей, методов и классов.
    • Для классов - нельзя иметь наследников
    • Для методов - нельзя перегрузить в наследниках (+ метод становится non-virtual)
    • Для полей - либо константа времени компиляции, либо константа времени выполнения
    • Почему стоит делать когда-то final: потому что иначе может нарушиться инвариант, который вы считаете, что сохраняется.
public class BlankFinal {
    private final int = 0; // константа времени компиляции
    public static final int N = 10; // константа времени компиляции
    private final int j; // константа времени выполнения
}
class A {
    A() {
        ...
        foo(); // foo() обязан быть final
        ...
    }
}

Инициализация

void f() {
    int i;
    i++; // не компилирутеся, обязательно нужно инициализировать.
}
public class StaticTest {
    static int i; 
    int j, h;
    static {
        i = 25;
        System.out.println("Hello");
    } // статическая секция инициализации
    {
        j = 8;
        h = 3;
    } // обычная секция инициализации

    // Обычная секция вызывается перед конструктором. Нужно, например, если есть общий для всех конструкторов код

    // Со статической секцией интереснее - она выпоняется ОДИН раз, примерно в тот момент, когда класс загружается в память.
    // Но откладывается на как можно поздний момент.
    // В момент загрузки класса в память статическая инициализация на самом деле ещё не произошла. 
    // Она произойдёт тогда, когда мы попытаемся обратиться к содержимому класса (точнее только если то, к чему мы обращаемся зависит от того, выполнилась статическая инициализация или нет)
}

"Грустно-смешная шутка про Java"

class X {
    static {
        System.out.println("Hello world");
        System.exit(1);
        // Это когда-то работало... без main написали Hello world.
        // Но сейчас, к счастью, нет.
    }
}

Исключения

Причины ошибок:

  1. Ошибки программирования
  2. Неверное использование API
  3. Доступ к внешним ресурсам
  4. Системные сбои

Всякое:

  1. Все исключения - полноценный объект
  2. Все исключения наследуются от класса Throwable
  3. От него наследуется Error и Exception
    • Exception - то, в чём так или иначе виновата наша программа
    • Error - то, в чём виновата виртуальная машина
  4. Если исключение не обрабатывается в классе, а вылетает дальше - указываем в throws
  5. Исключения - это долго
public static int parseInt(String s, int radix) throws /* exceptions */  {
    ...
}
try {
    n = Integer.parseInt(s);
    /*
    * Если исключение, то
    * Java машина останавливается и бежит вверх по стеку, ищем следующий
    * catch, если подошло - супер, иначе - бежим дальше
    * если не нашлось - пишем матерное сообщение в консоль
    */
} catch (NumberFormatException e) {
    System.out.print("Bad number, try again");
}
try {
    ...
} catch (IOException | NumberFOrmatException e) { // e имеет тип LCA
    ...
}
InputStream is = new FileInputStream("a.txt");
try {
    readFromInputStream(is);
} finally {
    is.close();
}

Стратегии обработки:

  1. Писать пустой catch - плохо
  2. Запись в лог - плохо (потому что это SideEffect)
  3. Содержательная обработка, например повтор операции

Try with resources

Документация по try-with-resources.

try (Input is = new FileInputStream("a.in")) {
    readFromInputStream(is);
}

// close будет вызван автоматически
// можно перечислить несколько ресурсов через ;

// На самом деле это просто синтаксический сахар!

В блоке try (...) создаваемые ресурсы должны имплементировать интерфейс AutoCloseable или Closeable (так у них появится метод close()).

В блоке try (...) можно создавать несколько ресурсов:

try (ZipFile zf = new ZipFile(zipFileName);
     BufferedWriter writer = newBufferedWriter(outputFilePath, charset))
 {
    ...
 }
 
 /*
    Вызов close() будет происходить в порядке обратном созданию объектов, т.е. в данном примере порядок будет следующий:
        1. writer.close();
        2. zf.close();
 */

Как рассахаривается try-with-resources:

try {
    InputStream is = new FileInputStream("filename.txt");
    Throwable t1 = null;

    try {
        // process file
    } catch (Throwable t2) {
        t1 = t2;
        throw t1;
    } finally {
        if (is != null) {
            if (t1 != null) {
                try {
                    is.close();
                } catch (Throwable closeException) {
                    t1.addSupressed(closeException);
                }
            } else {
                is.close();
            }
        }
    }
} catch (...) {
    
}