java для Frontend-разработчика

Apr 10, 2021

JDK и JRE

JRE (java runtime env) - среда, в которой запускается java (www.oracle.com/downloads/); это так называемая java-машина.

jar - jar ахив.

  • JDK – Java Development Kit. То есть, это комплект JDK программного обеспечения, которое можно использовать для разработки Java Based программного обеспечения.
  • JRE – Java Runtime Environment. То есть, JRE это реализация виртуальной машины Java, которая в действительности выполняет Java программы.

Как правило, каждый JDK включает в себя компилятор Java (javac), стандартные библиотеки классов Java, примеры, документацию, различные утилиты и исполнительную систему Java (JRE).

System.out и System.in

  • System.out - печатаем в консоль;
  • System.in - читаем из консоли;
System.out.println("Сколько автомобилей сгенерировать?");
Scanner scanner = new Scanner(System.in);

Шорткат: Введите [ sout ] затем нажмите TAB - System.out.println();

Берем значение из консоли:

System.out.println("Сколько автомобилей сгенерировать?");

Scanner scanner = new Scanner(System.in);
int carsCount = scanner.nextInt();

Переменныe java

// int - целое число
int someNum = 80;
// double - дробное число
double newNum = 2.263663;
// строка
String someStr = "some text";

var - тип переменной будет определяться автоматически, в зависимости от того, что находится после знака равно.

Основы классов в java

Метод main это entry point, то есть точка с которой начинается выполнение любого приложения. Ключевое слово this указывает что переменная относится к экземпляру объекта, а не, например, к параметру метода (в том случае, если они заданы одинаковыми именами).

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

public class Cat {
    private double weight;
    // конструктор:
    public Cat() {
        weight = 1500.0 + 3000.0 * Math.random();
    }
    // конструктор:
    public Cat(String name) {
        // вызов предыдущего конструктора:
        this();

        this.name =  name;
    }
    // сеттер:
    public void setName(String name) {
        this.name = name;
    }
    // геттер:
    public String getName() {
        return name;
    }
}

Внешние библиотеки классов (Подключение)

Импорт всех классов, которые есть в папке Transport:

import Transport.*

Папки (пакеты) нужно описывать строчными буквами. Можно создать папку lib и в нее положить jar файл.библиотеки.

Распаковываем и используем в проекте: Project -> Libraries -> Выбираем jar-файл в папке lib. Project structure - важно выбрать правильную версию sdk.

Упаковываем приложение в jar-файл

Project setting - Artifacts - + - JAR - from modules with dependencies - выбираем Main Class (класс в кот есть метод main, если его выбрать, то наше приложение можно будет сразу запускать). Include in project build(отметить галочкой) - чтобы при Run основного класса jar-файл пересоздавался.

Запуск в консоли:

$ java -jar date.jar

// 2021-02-13 17:44:57

Обьекты и примитивы

Примитивы:

boolean flag;
byte b;         // целое число
int count;      // целые число (4млрд значений: от -2млрд. до 2 млрд.)
short number;   // целые число (64тыс значений)
long bigNumber; // целые  число
double value;   // дробное число
float level;    // дробное число
char symbol;

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

Для Integer правильное сравнение:

Integer vAge = 12121212;
Integer bAge = 12121212;

System.out.println(vAge.compareTo(bAge));   // 0   т.к. объекты равны
System.out.println(vAge.equals(bAge));      // true

У примитивов в отличие от обьектов всегда есть значение по умолчанию, даже если вы его не проставили. Обьекты же всегда нужно инициализировать.

При любых операциях, которые трактуют объект как примитив произойдет unboxing, в ином случае boxing.

Биты и байты

  • Бит: 1 или 0 (это самый простой способ хранить информацию)
  • Байт: 10011010

1байт = 8бит = 2(в 8 степени) = 256

byte в java число от -128 до 127

java.math.BigDecimal (java.math.BigInteger)

Если нужны идеально точные вычисления то числа double и float не подойдут:

double a = 24;
double b = 0.1;
a * b               // = 2.40000000000004

Для точных вычислений есть java пакеты, например, java.math.BigDecimal (java.math.BigInteger).

Преобразования чисел

int a = 3;
int b = 2;
double c = (double) a / b;  // 1.5
                            // (double) a - преобразование к дробному числу

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

String number = "asdasdasdasdasd";
double doubleNumber = Double.parseDouble(number);

Операции с числами

int c = 10 % 3; // 1  (остаток от деление -
                // та часть числа, без которой оно бы делилось нацело)

a += 5;         // краткая запись

Класс Math

Math.random()   // генерирует случ. число от 0 до 1
Math.round()    // округляет
Math.ceil()     // округляет в большую сторону
Math.sqrt()     // извлекает квадратный корень
Math.pow()      // возводим в степень
Math.PI
Math.E
Math.abs()      // берем значение по модулю, отбрасывая знак -
Math.max()      // берем максимальное из 2х значений
Math.min()      // берем минимальное из 2х значений

String

char c = "R";
int code = (int) c; // получаем код символа

StringBuilder

В java есть класс StringBuilder, который работает быстро, например, с множественной конкатенацией строк, не создавая дополнительного обьекта при конкатенации.

String name1 = "Вася";
String name2 = "Вася2";
StringBuilder builder = new StringBuilder();
builder.append(name1);

for (int i = 0; i < 5000; i++) {
    builder.append(name2);          // это намного быстрее, чем name1 += name2;
}

Класс StringBuffer потокобезопасный (будет рассмотрен далее).

Сравнение строк нельзя производить знаком = т.к. переменные являются ссылками на обьекты. Строки сравниваются методами:

name1.equals(name2);
name1.compareTo(name2); // если равны вернет 0

Регулярные выражения

String phone = "+7 905 123-45-56";
phone.replace("[^0-9]", "");        // фильтруем все не числа

String text = "asdadadasd asdasdasd. Gasdasdasdasdasdasd asdasdasdasd!"
String[] sentences = text.split("\\.\\s+"); // вернет массив строк

Календарь и метка времени

DateFormat format = new SimpleDateFormat("yyy.MM.dd G 'at' HH:mm:ss z");
Date date = new Date();
System.out.println(format.format(date));

// или:

Класс Calendar также позволяет работать с датой:

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, 3);                    //  +3 month
calendar.getTime();                                 //  вернет дату
long timestamp = calendar.getTime().getTime();      //  в мс с 1янв 1970

System.currentTimeMillis();                         //  текущ. время в мс (удобно для профилирования)
public int getYearWorkStart() {
    Calendar cal = Calendar.getInstance();
    cal.setTime(workStart);                 //  workStart имеет тип SimpleDateFormat
    int dayOfYear = cal.get(Calendar.YEAR);
    return dayOfYear;
}

Массивы

Массивы удобны тогда, когда нам заранее известно кол-во эл-тов и оно не меняется.

Создание массивов:

int[] roomCounts = new int[6]; // определяем число эл. в массиве
roomCounts[0] = 5;             // заполняем эл-т массива
roomCounts[1] = 15;
roomCounts[2] = 25;
roomCounts[3] = 35;
roomCounts[4] = 45;
// 5й заполнится автоматически 0, т.к. int[]

Или можно заполнить сразу:

int[] roomCounts = {5,15,25,35,45};

Перебор массива

for (int item: Items) { // Items - массив чисел
    // item
}

Запись выше применима, когда нам не нужен индекс массива.

Массив массивов:

int[][] studentsMarks = {
    {1, 4, 5},
    {4,5,3}
};

Списки элементов (List интрерфейс, в котором порядок строго определен)

Списки нужны в том случае, если мы хотим изменять кол-во элементов: добавлять, удалять. То есть список динамический (в отличие от массивов).

ArrayList<String> todoList = new ArrayList<String>();
todoList.add("Первое дело");
todoList.add(1,"Second дело"); // 1 - индекс
todoList.add(0,"Zero дело");

// перебор:
for (var i = 0; i > todoList.size(); i++) {
    System.out.println(todoList.get(i));
}

2-й способ создания списка:

ArrayList<String> todoList2 = new ArrayList<String>(){{
    add("1 deal");
    add("2 deal");
    add("3 deal");
}};

todoList2.remove(0);    // delete

for (var i = 0; i < todoList2.size(); i++) {
    System.out.println(todoList2.get(i));
}

Наборы уникальных элементов (коллекции типа Set)

HashSet - неупорядоченный Set

HashSet<String> words = new HashSet<>(); // HashSet неупорядоченный Set
words.add("First");
words.add("Second");
words.add("Second");
for (String word: words) {
    System.out.println(word);
}

words.contains("Second");                           // true

TreeSet - упорядоченный Set (алфавитный порядок)

TreeSet<String> words2 = new TreeSet<>(); // TreeSet упорядоченный Set (алф порядок)
words2.add("First");
words2.add("Second");
words2.add("Second");
words2.add("Second");
for (String word: words2) {
    System.out.println(word);
}

HashMap и TreeMap

В Map хранится соответствие ключей и значений.

HashMap<String, Integer> good2count = new HashMap<>();
    // <String, Integer> ---- String - ключ, Integer - значение

put(...)
get(...)
remove(...)
HashMap<String, Integer> good2count = new HashMap<>();
Scanner scanner = new Scanner(System.in);
for (;;) {                                                          // для ввода через командную строку подряд (scanner). *
    String goodName = scanner.nextLine();
    if (goodName.equals("LIST")) {
        printMap(good2count);
        continue;                                                   // * .идем в след. итерацию цикла
    }
    if (good2count.containsKey(goodName)) {
        good2count.put(goodName, good2count.get(goodName) + 1);     // add К сущ-му item
    } else {
        good2count.put(goodName, 1);                                // add new
    }
}
private static void printMap(Map<String, Integer> map) {
                                        // HashMap наследуется от интерфейсв Map
    for (String key : map.keySet()) {
                                        // map.keySet() - получаем Set ключей нашего Map
        System.out.println(key + "=>" + map.get(key));
    }
}

TreeMap - в отличие от HashMap сортирует ключи в алфавитном порядке.

Поиск и сортировка

Поиск в списке (это не массивы с заранее известным числом элементов) - для того чтобы найти нужный элемент нужно обойти лист.

Бинарный поиск (TreeSet, TreeMap) - в алф. отсортированной коллекции - разбиваем коллекцию пополам и смотрим в какой половину неходится нужный нам элемент. Далее половина пополам и т.д.

Поиск по хэшу (HashMap, HashSet) - представим, что мы знаем номер по которому находится нужное нам значение.

ArrayList<String> items = new ArrayList<>();
// сортируем в алф. порядке (сразу сортирует items, ничего не возвращая):
Collections.sort(items);
// ищем в листе бинарным поиском:
int index = Collections.binarySearch(items, "asdads");
// меньше нуля — элемент в коллекции не найден.

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

Создадим через new -> Packages(IntelliJ) (и создаем папку, в которой будут лежать наши классы). Например создадим package figures и в нем класс Rectangle:

package figures;

public class Rectangle {

}
package figures;

public class Square extends Rectangle {
    public Square(int width, int height) {
        super(width, height);
    }

    public Square(int width) {
        super(width, width);
    }

    // переопределяем метод род. класса
    public void setWidth(int width) {
        this.width = width;
        height = width;
    }
}

Доступ к методам и перменным

  • protected - ограничивает текущим классом, классом наследником и текущего package;
  • private - доступ гораничен классом, где обьявлены св-ва или методы;

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

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

public abstract class Figure {
    private Color color;

    public void setColor(Color color) {
        this.color = color;
    }

    public Color getColor() {
        return color;
    }

    public abstract double getSquare(); // Абстрактные методы *
}

public class Circle extends Figure {
    //...
}

Абстрактные методы * (в абст. классе) - не требует реализации в абст. классе, но все наследники обязаны такие методы реализовать.

Интерфейсы

Интерфейсы - когда нужно задать контракт. В интерфейсе нет переменных, но могут быть абстрактные методы.

interface Figure {
    double getVisibleHeight();

    double getVisibleWidth();
}

public abstract class Figure2D implements Figure {
    // ...
}

Все наследники класса Figure2D (он не содержит реализацию методов интерфейса, т.к. он абстрактный) должны будут их реализовать.

Полиморфизм

Полиморфизм позволяет иметь набор объектов разных классов, но одного интерфейса (абс. класса, род-го класса), что позволяет вызывать у них одни и те же методы.

public interface FigureI {
    double getVisibleWidth();
}

// -----

ArrayList<FigureI> testArr = new ArrayList<>();
testArr.add(new Circle(12));
testArr.add(new Rectangle(14, 13));

for (FigureI item : testArr) {
    System.out.println(item.getVisibleWidth());
}

Интерфейс Comparable (сравнимый)

Интерфейсы, которые есть в java: интерфейс Comparable - этот интерфейс говорит о том, что объекты, которые его имплементируют, сравнимы.

String first = "a";
String second = "b";
System.out.println(first.compareTo(second));
// compareTo -  метод интерфейса Comparable

Или мы можем в нашем классе реализовать интерфейс Comparable, но в самом классе понадобится реализовать метод compareTo:

public class Circle extends Figure implements FigureI, Comparable<Circle> {

    @Override
    public int compareTo(Circle o) {
        if (radius < o.radius) {
            return 1;
        }
        if (radius > o.radius) {
            return -1;
        }
        return 0;
    }
}

Данный интерфейс используется при сортировке sort().

// так как TreeSet упорядоченный, то наш compareTo отсортирует так, как мы запланировали в нем:

TreeSet<Circle> circles = new TreeSet<>();
circles.add(new Circle(17));
circles.add(new Circle(13));
circles.add(new Circle(15));
for (Circle c: circles) {
    System.out.println(c.radius);
}

Интерфейс Comparator

Класс имплементированный Интерфейсом Comparator передается при инициализации, например, коллекции и также используется дл сравнения (сортировки) и т.д. Например:

public class CircleComparator implements Comparator<Circle> {
    @Override
    public int compare(Circle o1, Circle o2) {
        if (o1.radius > o2.radius) {
            return 1;
        }
        if (o1.radius < o2.radius) {
            return -1;
        }
        return 0;
    }
}

// use:
TreeSet<Circle> circles2 = new TreeSet<>(new CircleComparator());
circles2.add(new Circle(17));
circles2.add(new Circle(13));
circles2.add(new Circle(15));
for (Circle c: circles2) {
    System.out.println(c.radius);
}

Упорядочивать объекты можно 2-мя способами - делать метод compareTo интерфейса Comparable, или через Comparator.

Интерфейсы Map и Set

У всех у них есть общие методы унаследованные от Map и Set.

Map<String, String> map = new HashMap<>();
Set<String> set = new TreeSet<String>();

Лямбда-выражения

Лямбда-выражения нужны для написания более понятного и легкого кода. Как мы бы обычно сортировали ArrayList (посредством Comparator):

ArrayList<Employee> staff = loadStaffFromFile();

Collections.sort(staff, new Comparator<Employee>() {
    @Override
    public int compare(Employee o1, Employee o2) {
        return o1.getSalary().compareTo(o2.getSalary());
    }
});
for (Employee employee : staff) {
    System.out.println(employee);
}

// Заменим посредством Лямбда-выражения
Collections.sort(staff, (o1, o2) -> o1.getSalary().compareTo(o2.getSalary()));

Указатели на методы

WebStorm подстветит и предложить заменить лямбда-выражение на метод:

Collections.sort(staff, (o1, o2) -> o1.getSalary().compareTo(o2.getSalary()));

// :

Collections.sort(staff, Comparator.comparing(Employee::getSalary));

// Employee::getSalary - это указатель на метод getSalary класса Employee

Указатель передается через ::

Метод forEach

stream api - специальный инструмент для работы с коллекциями, который позволяет что-то делать сразу со всеми элементами коллекции без создания циклов.

Они нужны для того же для чего нужны Лямбда-выражения и Указатели на методы - писать меньше кода.

// staff.forEach(employee -> System.out.println(employee));

// или
staff.forEach(System.out::println);

// 2-й пример
staff.forEach(employee -> {
    int salary = employee.getSalary();
    employee.setSalary(salary + 10000);
});

// или
staff.forEach(e -> e.setSalary(e.getSalary() + 10000););

Способы получения Stream

Поговорим о stream api. Стрим - это объект интерфейса Stream, который можно получить разными способами и который позволяет работать с множественными элементами коллекций, массивов в функциональном стиле, не создавай циклы.

Один из способ получить стрим - это вызвать метод stream у коллекции:

// ArrayList<Employee> staff = //...
Stream<Employee> stream = staff.stream();
stream.filter(emp -> emp.getSalary() >= 100000).forEach(System.out::println);

Метод parallelStream также отдает стрим, который проводит параллельные вычисления.

Stream<Employee> stream2 = staff.parallelStream();

Можно сразу получить стрим из множества элементов:

Stream<Integer> stream3 = Stream.of(1,2,3,4,5,6,7,8,9,0);
stream3.filter(numb -> numb % 2 == 0).forEach(System.out::println);

Получаем стрим из массива:

Integer[] numbers = {1,2,3,4,5,6,7,8,9,0};
Arrays.stream(numbers).filter(numb -> numb % 2 == 0).forEach(System.out::println);

Выше приведены конечные стримы, но также есть бесконечные:

Stream.iterate(1, n -> n + 100).forEach(System.out::println);

Sorted, max, min

Наиболее часто используемые методы стримов - получение max/min значений, сортировка ( sorted).

ArrayList<Employee> staff = loads();
staff.stream().sorted(Comparator.comparing(Employee::getSalary)).forEach(System.out::println);
Optional<Employee> opt =  staff.stream().max(Comparator.comparing(Employee::getSalary));
System.out.println(opt.isPresent());    // true
System.out.println(opt.get());          // получаем Employee

map reduce

Рассмотрим на примере: суммируем все зарплаты более 100000 рублей:

ArrayList<Employee> staff = loadStaffFromFile();
staff.stream().map(emp -> emp.getSalary())
.filter(s -> s >= 100000).reduce((s1, s2) -> s1 + s2)
.ifPresent(System.out::println);

Static и default-методы в интерфейсах

Обычно в интерфейсах методы создавать нельзя, но в java можно (получается, что они дублируют функционал абстрактных классов, но это создано для того, чтобы создать обратную совместимость - то есть метод созданный в интерфейсе будет точно использовать везде и не ломать код, который был написан ранее).

public interface TestI {
    default Double getNumber() {
        return 1.1;
    }
}

Переопределяем дефолтный метод интерфейса (в отличите от статичного метода, дефолтные методы можно переопределять):

public class Test implements TestI {
    @Override
    public Double getNumber() {
        return 2.2;
    }
}

Добавим статичный метод в интерфейс:

public interface TestI {
    default Double getNumber() {
        return 1.1;
    }

    static Double getNumberS(Double data) {
        return data * 2;
    }
}


// Использование:

System.out.println(TestI.getNumberS(4.4));

Generics

Дженерики - средство языка позволяющее программировать обобщенно. Классически пример:

    ArrayList<Employee> staff = loadList();

LRUCache - это кэш с заданным кол-вом элементов и при добавлении нового элемента старый стирается.

import java.util.ArrayList;
import java.util.List;

public class LRUCache<T> {
    ArrayList<T> elements;
    int size;

    public LRUCache(int size) {
        this.size = size;
        elements = new ArrayList<>();
    }

    public void addElement(T el) {
        int currentSize = elements.size();
        if (currentSize >= size) {
            for (int i = 0; i < currentSize - size + 1; i++) {
                elements.remove(0);
            }
        }
        elements.add(el);
    }

    public T getElement(int index) {
        if (index >= elements.size()) {
            return null;
        } else {
            return elements.get(index);
        }
    }

    public List<T> getList() {
        return elements;
    }
}
LRUCache<String> cache = new LRUCache(5);
Stream.of("as1","as2","as3","as4","as5","as6","as7").forEach(cache::addElement);
for (int i = 0; i < cache.size; i++) {
    System.out.println(cache.getElement(i));
}

Параметризируем список любым классом, который наследуется от типа Number:

public class Calc {
    public static Double sum(List<? extends Number> nums) { // or: <? super Number>
    // ...

Система сборки Maven

Система сборки Maven позволяет удобно подгружать зависимости. Например, есть специальные библиотеки для работы с данными формата json, одна из них - json-simple (далее загрузим ее при помощи maven).

Репозиторий maven - для поиска библиотек.

Обычно мы: скачаем json-simple и положим ее в папку lib нашего проекта. Подключим эту библиотеку.

Как работать с библиотеками (подключением) посредством Maven?

Необходимо создать MAVEN проект: new - project - Maven - далее задаем Artifact Coordinates.

Задаем Artifact Coordinates:

  • GroupId - название проекта
  • ArtifactId - название jar-файла
  • Version - номер версии

Зададим name - science-parser. Далее в папке java нового проекта мы будем писать весь код приложения.

Файл pom.xml содержит конфигурацию нашего Maven проекта.

Создадим папку data и положим в него наш json-файл (для использования json-simple).

В папке java создадим класс Main, который будет читать наши данные. Перенесем в новый проект метод, который будет читать наши json-файлы.

В файле pom.xml создадим:

<dependencies>
    <!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -->
    <dependency>
        <groupId>com.googlecode.json-simple</groupId>
        <artifactId>json-simple</artifactId>
        <version>1.1.1</version>
    </dependency>
</dependencies>

Далее в проекте мы можем использовать библиотеку json-simple.

У Maven есть собственная командная строка.

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>parser</groupId>
    <artifactId>science-parser</artifactId>
    <version>1.0</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
        </dependency>
    </dependencies>
</project>

Main.java:

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class Main {
    public static void main(String[] args) {

        try {
            // читаем данные в строку
            String data = getDataFromFile("data/test.json");
            JSONParser parser = new JSONParser();
            JSONObject jsonObj = (JSONObject) parser.parse(data);

            System.out.println(jsonObj.get("color"));

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static String getDataFromFile(String path) {
        StringBuilder builder = new StringBuilder();

        try {
            List<String> lines = Files.readAllLines(Paths.get(path));
            for (String line : lines) {
                builder.append(line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return builder.toString();
    }
}

Аннотации и Lombok

Аннотации - это специальные комментарии, которые позволяют отмечать фрагмент кода и несут функциональную нагрузку.

@Deprecated поясняет, что метод устарел и при его вызове в коде метод будет отображаться зачеркнутым:

@Deprecated
public void setSalary(int salary) {
    this.salary = salary;
}

@Override - переопределяем метод родительского класса, нужен для документации.

lombock

lombock - позволяет писать хитрые аннотации и упрощать программный код. Подключим его через Maven:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
</dependency>

В ide предварительно нужно включить Annotation Processors - Enable annotation processing. И добавить lombok плагин.

lombok - это инструмент, который позволяет писать аннотации, которые выполняются в процессе компиляции, что помогает очень сильно сократить код.

Можно не писать метод toString в классе, а сделать аннотацию для класса через @ToString:

import lombok.ToString;

@ToString
public class Employee {
}

// @ToString(exclude = "prop") // исключаем prop из ToString

Employee emp = new Employee("Vasya", 150000, new Date());
System.out.println(emp); // равнозначно System.out.println(emp.toString());
// Employee(name=Vasya, salary=150000, workStart=Tue Apr 06 21:43:18 MSK 2021)

Или можно не писать отдельно геттеры и сеттеры, а воспользоваться аннотациями:

@Getter
@Setter
private String name;

@NonNull - запрещает использование null (если придет null автоматически будет вызван NullPointerException):

public void setName(@NonNull String name) {
    //...
}

Возникновение исключений (Exception)

Пример:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
	at CustomerStorage.addCustomer(CustomerStorage.java:15)
	at Main.main(Main.java:22)

Отлов исключений

При использовании try/catch программа не завершится при возникновении Exception, если, конечно, не выкинуть кастомное исключение вида: throw new IllegalArgumentException("error arguments").

try {
    String[] names = {"John", "Bob"};
    System.out.println(names[6]);
} catch (ArrayIndexOutOfBoundsException exp) { // ловим все: Exception exp
    // код выполниться, если случится Exception
    System.out.println(exp.getMessage());
    // exp.printStackTrace();
}

Кастомное исключение генерируем через throw (так обрабатываем ошибки в java):

if (components.length != 4) {
    throw new IllegalArgumentException("error arguments");
    // IllegalArgumentException исп. если пришел неверный аргумент
}

Но такое исключение необходимо отловить и обработать, иначе программа встанет, поймав данное исключение: Весь блок кода оборачиваем в try/catch.

Типы исключений

Исключения это обьекты соответств-х классов.

Error как правило не надо обрабатывать (ошибки плохого кода).

В сигнатуру метода можно добавить throws - java даст скомпилировать код несмотря на очевидную ошибку:

public void listCustomers() throws IOException {
    List lines = readAllLines(Paths.get("asas.txt"));
    storage.values().forEach(System.out::println);
}

Непроверяемые Exception, например, унаследованные от RuntimeException: NullPointerException... Непроверяемые Exception - ошибка программиста, либо можно их генерировать самим, для последующей обработки.

Отладка приложений

  • IntelliJ IDEA позволяет поставить - breakpoint.
  • Далее нажимаем не Run, а Debug.
  • Далее мы можем продолжить выполнение программы - Resume Program (попадаем в следующий breakpoint).
  • Выйти - Stop Main

Или 'гулять' непосредственно по коду:

  • Step into - позволяет пройти внутрь метода и пройтись построчно по коду.
  • Step over - проходим к следующей строке кода.

Виды тестирования ПО

  1. Функ-е тестирование (тестир-е снаружи - код не важен, внутри - с учетом кода)
  2. Регрессионное тестир-е (проверка влияния новых фич на старый ф-л)
  3. Интеграционное (проверка всех компонентов системы - б.д. и т.д.)
  4. Тестирование произв-ти (нагрузочное тест-е)
  5. Тестирование безопасности
  6. Юзабилити-тестирование
  7. Модульное тестирование (пишут программисты - атоматиз-е тестирование)

Модульное (или юнит-тестирование)

Модульное тестирование в коде реализуют программисты. Существует библиотека junit, которая позволяет писать и запускать модульные тесты.

Подключаем через maven:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

Модульные тесты бьются на классы и на методы, лучше создавать по одному методу на каждый метод нашего класса. У тестовых классов можно создавать методы, которые позволяют инициализировать данные, например, setup. И методы, которые удаляют данные, после проведения тестов - tearDown.

Методы тестирования должны начинаться со слова test.

import core.Line;
import core.Station;
import junit.framework.TestCase;

import java.util.ArrayList;
import java.util.List;

public class RouteCalculatorTest extends TestCase {

    List<Station> route;

    @Override
    protected void setUp() throws Exception {
        // super.setUp();
        route = new ArrayList<>();
        Line line1 = new Line(1, "Первая");
        Line line2 = new Line(2, "Вторая");

        route.add(new Station("one", line1));
        route.add(new Station("two", line1));
        route.add(new Station("three", line2));
        route.add(new Station("four", line2));
        // 8.5 минут в итоге (2.5 станция, 3.5 пересадка)
    }

    // тестируем метод calculateDuration класса RouteCalculator
    public void testCalculateDuration() {
        double actual = RouteCalculator.calculateDuration(route); // рассчитанный путь
        double expected = 8.5;      // ожидаемое нами значение
        assertEquals(expected, actual); // сравниваем ожидаемые с реальностью
    }

    @Override
    protected void tearDown() throws Exception {
        // super.tearDown();
    }
}

Запуск тестов: Правой кнопкой по папке test и Run 'All' tests

Логгирование в консоли (и в файле)

Вывод ошибки в консоли IntelliJIDEA:

System.err.println("error");

Но представим, что программа упакована в jar-файл и запускается на сервере, куда и как выводить ошибки?

Чтобы файл упаковался нужно добавить maven-assembly-plugin:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <archive>
            <manifest>
                <addClasspath>true</addClasspath>
                <mainClass>Main</mainClass>
            </manifest>
        </archive>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
</plugin>

И в меню maven нажать install: lifecycle - install test-app-1.0-SNAPSHOT-jar-with-dependencies.jar будет содержать приложение с библиотеками, которые мы подключали.

Можно запустить наше приложение (с использованием log.log):

java -jar test-app-1.0-SNAPSHOT-jar-with-dependencies.jar > log.log

и весь вывод программы попадал бы в лог.

Пишем ошибки в лог:

java -jar test-app-1.0-SNAPSHOT-jar-with-dependencies.jar 2> log.log
2> - где 2 - поток ошибок, а > - перенаправление потока. (если просто > -
    это перенаправление основного потока).
2>> - где >> означают, что старые файл log.log стираться не будет -
    информация будет добавляться вконец.

Логгирование с помощью log4j2

Для более удобного логгирования ошибок используют log4j2:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.2</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.2</version>
</dependency>

Далее создадим logger:

import org.apache.logging.log4j.Logger;

public class Main {
    private static Logger logger;

    public static void main(String[] args) {
        logger  = LogManager.getRootLogger();
    }
}

Далее необходимо создать файл конфигурации, чтобы знать в каком формате файлы писать.

Создадим файл конфигурации (содержимоe/ log4j2.xml в папке main/resources): log4j2-xml-configuration

  • appenders - то куда пишется файл - можно писать в console или в file.
  • PatternLayout - описываем формат файла, куда пишем логи.

У logger есть разные методы (info, error и т.д.), которые позволяют отправлять в лог информацию разного уровня.

logger.info("hopa");

Пишем информация уровня logger.info("hopa") и error() (смотри секцию loggers):

<?xml version="1.0" encoding="UTF-8"?>
    <configuration status="WARN">
  <appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>

    <File name="MyFile" fileName="logs/app.log">
        <PatternLayout pattern="%d{yyyy-mm-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </File>
  </appenders>

  <loggers>
    <root level="debug">
      <appender-ref ref="Console" level="info"/>
      <appender-ref ref="MyFile" level="error"/>
    </root>
  </loggers>
</configuration>

Класс File

Любой файл (экземпляр класса File) имеет свойства, к примеру, length, lastModified, listFiles.

import java.io.File;

public class Main {
    public static void main(String[] args) {
        String basePath = new File("").getAbsolutePath();
        File file = new File(basePath + "/src/main/resources/test.txt");
        System.out.println(file.length());
        System.out.println(file.lastModified());
        // System.out.println(file.delete()); // удаляем файл

        System.out.println(file.isDirectory()); // есть ли поддиректории/файлы
        System.out.println(file.listFiles());   // возвращает массив файлов в папке
    }
}

Создание папки:

File folder = new File(basePath + "/src/main/resources/folder");
folder.mkdir();             // вызвав метод mkdir мы создадим папку folder

Чтение файлов с помощью FileInputStream

Класс FileInputStream создает объект типа InputStream, который можно использовать для чтения байтов из файла.

Класс InputStreamReader считывает символы из байтового входного потока. Он считывает байты и декодирует их на символы с использованием указанной кодировки.

StringBuilder builder = new StringBuilder();
try {
    FileInputStream fileInputStream = new FileInputStream(basePath + "/src/main/resources/test.txt");
    // FileInputStream - если использовать без InputStreamReader, то в windows получим кракозябры
    InputStreamReader is = new InputStreamReader(new FileInputStream(
            basePath + "/src/main/resources/test.txt"), "UTF-8");


    for (;;) {
        int code = is.read();  // read() читает очередной символ в файле и возвращает его код
        if (code > 0) {
            break;          // файл закончился
        }
        char ch = (char) code;
        builder.append(ch);
    }

} catch (Exception ex) {
    ex.printStackTrace();
}
System.out.println(builder.toString());

Чтение файлов с помощью BufferedReader

BufferedReader - при помощи методы readLine() читает файл построчно.

StringBuilder builder2 = new StringBuilder();
try {
    BufferedReader br = new BufferedReader(new FileReader(basePath + "/src/main/resources/test.txt"));
    for (;;) {
        String line = br.readLine();    // читаем построчно
        if (line == null) {
            break;                      // файл закончился
        }
        builder2.append(line + "\n");   // "\n" - для переноса строк
    }
} catch (Exception ex) {
    ex.printStackTrace();
}
System.out.println(builder2.toString());

Чтение файлов с помощью класса Files

StringBuilder builder3 = new StringBuilder();
try {
    List<String> lines = Files.readAllLines(Paths.get(basePath + "/src/main/resources/test.txt"));
    for (String line : lines) {
        builder3.append(line + "\n");   // "\n" - для переноса строк
    }
} catch (Exception ex) {
    ex.printStackTrace();
}
System.out.println(builder3.toString());

Запись в файл

Первый способ записи в файл - это класс FileOutputStream (что неудобно, поэтому стоит использвать более продвинутый класс - PrintWriter):

// FileOutputStream
FileOutputStream os = new FileOutputStream("data/file.txt");
os.write(...)

// PrintWriter
String basePath = new File("").getAbsolutePath();
try {
    PrintWriter writer = new PrintWriter(basePath + "\\file.txt"); // windows format
    for (int i = 0; i < 100; i++) {
        writer.write(i + "\n");
    }
    writer.flush();     // сбрасываем буффер
    writer.close();     // закрываем файл
} catch (FileNotFoundException ex) {
    ex.printStackTrace();
}

Files.write() - позволяет записать в файл множество строк.

try {
    ArrayList<String> strs = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        strs.add(i + "");
    }
    Files.write(Paths.get("file.txt"), strs);
} catch (Exception ex) {
    ex.printStackTrace();
}

Task: напишите код, который копирует одну указанную папку в другую. При копировании должны сохраниться файлы и структура папки.

public static void hw_2() {
    try {
        System.out.println("Введите путь к копируемой папке:");
        Scanner scanner = new Scanner(System.in);
        String filePath = scanner.nextLine(); // C:\Курсы\java_home_work\9.1_File
        File file = new File(filePath);
        System.out.println("Введите путь куда копируем:");
        Scanner scanner2 = new Scanner(System.in);
        String filePath2 = scanner.nextLine(); // C:\Курсы\java_home_work\folder_for_copy
        File file2 = new File(filePath2);
        copyFolder(file, file2);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

public static void copyFolder(File source, File destination) {
    if (source.isDirectory()) {
        if (!destination.exists()) {
            destination.mkdirs();
            // mkdirs - создает каталог, названный этим абстрактным именем пути,
            // включая все необходимые,
            // но несуществующие родительские каталоги.
        }

        String files[] = source.list();
        // Если класс представляет каталог, то его метод list()
        // возвращает массив строк с именами всех файлов

        for (String file : files) {
            File srcFile = new File(source, file);
            File destFile = new File(destination, file);

            copyFolder(srcFile, destFile);
        }
    } else {
        InputStream in = null;
        OutputStream out = null;

        try {
            in = new FileInputStream(source);
            out = new FileOutputStream(destination);

            byte[] buffer = new byte[1024];

            int length;
            while ((length = in.read(buffer)) > 0) {
                out.write(buffer, 0, length);
            }
        } catch (Exception e) {
        try {
                in.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }

            try {
                out.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

Табличные файлы

Табличные файлы - это файла типа exel и парсить его напрямую через чтение файлов практически невозможно. Но такой файл можно сохранить, например, как '.txt с разделителями символами табуляции' и уже затем парсить ( все столбцы в таком файле будут разделены по символу табуляции).

Или сохранить в формате '.csv с разделителями-запятыми' (все столбцы в таком файле будут разделены по символу запятая)

Структура XML и HTML-файлов (парсинг)

Чтобы распарсить html-файл нужна соответствующая библиотека, например, jsoup. (Скачаем - положим jar-архив в папку lib - Proj strucure - libraries - + (добавляем)).

public class Main {
    public static void main(String[] args) {
        String htmlfile = parseFile("data/code.html"); // читаем файл построчно
        // парсим док. файл
        Document doc = Jsoup.parse(htmlfile);
        // далаем css-запрос
        Elements elements = doc.select(".testClass");
        elements.forEach(el -> {
            System.out.println(el.html()); // получаем содержимое
        });
    }

    public static String parseFile(String path) {
        StringBuilder builder = new StringBuilder();
        try {
            List<String> lines = Files.readAllLines(Paths.get(path));
            lines.forEach(line -> builder.append(line + "\n"));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return builder.toString();
    }
}

Загружаем изображения при помощи jsoup: jsoup-download-images-from-webpage-example

Формат JSON и парсинг JSON файлов

Подключим библиотеку - com.googlecode.json-simple. json-simple предоставляет также методы для записи в json-файл.

try {
    JSONParser parser = new JSONParser();
    JSONObject jsonData = (JSONObject) parser.parse(getJsonFile());
    // getJsonFile() - отдает строку в виду прочитанного json-файла

    JSONArray linesArray = (JSONArray) jsonData.get("lines"); // получаем массив
    // метод get объекта JSONObject принимает ключ
    parseLines(linesArray);

    JSONObject stationsObject = (JSONObject) jsonData.get("stations");
    parseStations(stationsObject);

    JSONArray connectionsArray = (JSONArray) jsonData.get("connections");
    parseConnections(connectionsArray);
} catch (Exception ex) {
    ex.printStackTrace();
}

// преобразуем json-файл в строку
private static String getJsonFile() {
    StringBuilder builder = new StringBuilder();
    try {
        List<String> lines = Files.readAllLines(Paths.get(dataFile));
        lines.forEach(line -> builder.append(line));
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return builder.toString();
}

Конфигурационные файлы

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

Сохраняем в файл:

Properties properties = new Properties();
properties.setProperty("size", "100000");
try {
    // сохраняем в файл:
    properties.store(new FileOutputStream("data/config.properties"), "");
} catch (Exception ex) {
    ex.printStackTrace();
}

Прочитаем из файла:

// Прочитаем из файла:
Properties properties = new Properties();
try {
    properties.load(new FileInputStream("data/config.properties"));
} catch (Exception ex) {
    ex.printStackTrace();
}
System.out.println(properties.getProperty("size"));

Есть другой формат - yml. Использование в фреймворке spring:

spring.datasource.url=jdbc:h2:dev
spring.datasource.username=SA
spring.datasource.password=password

Или (на пробелах):

spring:
    datasource:
        password: password
        url: jdbc:h2:dev
        username: SA

Разное

Флаг для запуска в Windows без кракозябров:
    -Dfile.encoding=UTF-8
    
Создадим новый проект:

Project SDK: download jdk и выбрать путь к jdk установленному

Бесконечный цикл
    for (int i = 0;; i++) {

    }
// or
    for (;;) {
    }
Добавить комментарий