Core Java. Лекция 8

Lambdas, method references. JDBC API

Иван Пономарёв, КУРС/МФТИ

Callbacks: До появления лямбд

public interface ActionListener {
  void actionPerformed(ActionEvent event);
}

// ----использование----
String text = ...
new Timer(1000, new ActionListener(){
  @Override
  public void actionPerformed(ActionEvent e) {
    ... //'text' var is available here
    }
  });

Callbacks: Comparator

String[] friends = {"Peter", "Paul", "Mary"};

Arrays.sort(friends,
  new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
      return o1.length() - o2.length();
  }
});

Предикат

File[] hiddenFiles = new File(".").listFiles(
  new FileFilter() {
    public boolean accept(File file) {
      return file.isHidden();
    }
  }
);

Welcome lambda expressions!

//single-line
(String first, String second) ->
  first.length() - second.length()

//multi-line with int return
(String first, String second) -> {
  if (first.length() < second.length()) return -1;
  else if (first.length() > second.length()) return 1;
  else return 0;
}

//no-arg, void return
() -> System.out.println("Hello!");

Что из этого — валидные лямбда-выражения?

  1. () → {}

  2. () → "Raoul"

  3. () → {return "Mario";}

  4. (Integer i) → return "Alan" + i

  5. (String s) → {"Iron Man"}

Ответ: 1-3. Если лямбда с фигурными скобками, обязательно нужен return. Если без них, то return не нужен!

Чему можно присваивать лямбды?

  • Функциональный интерфейс — такой, у которого не более одного абстрактного метода (понятно, что запускать).

  • Может быть помечен аннотацией @FunctionalInterface, хотя это не обязательно.

  • Если метод интерфейса подходит по параметрам и возвращаемому значению лямбды — welcome.

//Типы аргументов лямбд указывать не надо: type inference!
ActionListener = e -> {...}
Comparator<String> c = (s1, s2) -> s1.length() - s2.length();

Void-compatibility

Оба варианта скомпилируются:

final List<String> list = ...

//Predicate.test возвращает boolean
Predicate<String> p = s -> list.add(s);

//Consumer.accept возвращает void!
Consumer<String> c = s -> list.add(s);

Object — не функциональный интерфейс!

//не скомпилируется
Object o = ()->{};

//Скомпилируется!
Runnable r = ()->{};

//Тоже скомпилируется
Object o = (Runnable) ()->{};

В итоге имеем

Anonymous Class

Lambda

new Timer(100, new ActionListener(){
 @Override
 public void
   actionPerformed(ActionEvent e) {
     ...
   }
});
new Timer(100, e -> {...});
Arrays.sort(friends,
 new Comparator<String>() {
   @Override
   public int compare(
     String o1, String o2) {
    return o1.length()-o2.length();
 }
});
Arrays.sort(friends, (s1, s2)->
  s1.length() - s2.length());

Замыкания (closures)

void repeatMessage(String text, int delay) {
  ActionListener  listener = event -> {
    //переменная text доступна внутри лямбды!
    System.out.println(text);
  }
  new Timer(delay, listener).start();

}
  • «Ингредиенты» лямбды:

    • Код

    • Параметры

    • Свободные («захваченные») переменные, которые должны быть effectively final

Effectively final only

int start = ...

for (int i = 0; i < count; i++) {
  ActionListener linstener = event -> {
    start--; //ERROR: Can't mutate captured value

    //ERROR: Cannot refer to changing i
    System.out.println(i);
  }
}

(Effectively final переменные — это такие, что они либо уже final, либо, если на них проставить final, код всё равно будет компилироваться)

Method references: ещё короче, ещё эффективнее

event -> System.out.println(event)
System.out::println
(s1,s2)->s1.compareToIgnoreCase(s2)
String::compareToIgnoreCase
(x, y) -> Math.pow(x,y)
Math::pow

Три способа определить Method reference

object::method

(x, y, z…​) → object.method(x, y, z…​)

Class::instanceMethod

(x, y, z…​) → x.instanceMethod(y, z…​)

Class::staticMethod

(x, y, z…​) → Class.staticMethod(x, y, z…​)

Constructor/Array Constructor Reference

Class::new

(x, y, z…​) → new Class(x, y, z…​)

Class[]::new

(int i) → new Class[i]

IDE поможет, но разница есть!

obj = null;

//NPE только в момент запуска лямбды!!
//obj должен быть effectively final!
doSmth(x -> obj.method(x));

//NPE здесь и сейчас
//obj не обязан быть effectively final
doSmth(obj::method)

Методы, специально созданные, чтобы быть method references

//Да ладно, разве трудно на null проверить??
Objects.isNull(Object obj)...
Objects.nonNull(Object ob)...


list.removeIf(Objects::isNull);

stream.filter(Objects::nonNull)...

Готовые функциональные типы

Functional InterfaceParameter TypesReturn TypeAbstract Method NameDefault Methods

Runnable

none

void

run

Supplier<T>

none

T

get

Consumer<T>

T

void

accept

andThen

BiConsumer<T, U>

T, U

void

accept

andThen

Комбинирование консьюмеров

Consumer<String> foo = ...
List<String> list = ...

//Составной консьюмер, отправляющий
//объект сначала в первый, потом во второй
Consumer<String> bar = foo.andThen(list::add);

Функции

Functional InterfaceParameter TypesReturn TypeAbstract Method NameDefault Methods

Function<T, R>

T

R

apply

compose, andThen, identity

BiFunction<T, U, R>

T, U

R

apply

andThen

Композиция функций и identity

f.andThen(g)

g(f(x))

f.compose(g)

f(g(x))

Function.identity()

x → x

Почему у BiFunction нет compose, только andThen?

Операторы

Functional InterfaceParameter TypesReturn TypeAbstract Method NameDefault Methods

UnaryOperator<T>

T

T

apply

compose, andThen, identity

BinaryOperator<T>

T, T

T

apply

andThen, maxBy, minBy

Композиция операторов

  • UnaryOperator<T> extends Function<T,T>, поэтому compose, andThen и identity работают так же.

  • BinaryOperator<T> extends BiFunction<T,T,T>, поэтому andThen работает так же

  • Статические методы minBy и maxBy формируют операторы min(x,y) и max(x,y) из компаратора.

​Предикаты​

Functional InterfaceParameter TypesReturn TypeAbstract Method NameDefault Methods

Predicate<T>

T

boolean

test

and, or, negate, isEqual

BiPredicate<T, U>

T, U

boolean

test

and, or, negate

Композиция предикатов

Predicate<T> a = ...
Predicate<T> b = ...

a.and(b).negate(); // ! (a(x) & b(x))

//Двуместный предикат (x,y)-> Objects.equals(x, y)
Objects::equals

//Одноместный предикат y -> Objects.equals(x, y)
Predicate.isEqual(x)

Функциональные интерфейсы для примитивных типов

p, q is int, long, double; P, Q is Int, Long, Double

Functional InterfaceParameter TypesReturn TypeAbstract Method Name

BooleanSupplier

none

boolean

getAsBoolean

PSupplier

none

p

getAsP

PConsumer

p

void

accept

ObjPConsumer<T>

T, p

void

accept

PFunction<T>

p

T

apply

PToQFunction

p

q

applyAsQ

ToPFunction<T>

T

p

applyAsP

ToPBiFunction<T, U>

T, U

p

applyAsP

Функциональные интерфейсы для примитивных типов (продолжение)

Functional InterfaceParameter TypesReturn TypeAbstract Method Name

PUnaryOperator

p

p

applyAsP

PBinaryOperator

p, p

p

applyAsP

PPredicate

p

boolean

test

Дефолтные методы Map interface

Работают атомарно в ConcurrentHashMap!

V computeIfAbsent(K key,
  Function<? super K, ? extends V> mappingFunction)

V computeIfPresent(K key,
  BiFunction<? super K, ? super V, ? extends V> remappingFunction)

V compute(K key,
  BiFunction<? super K, ? super V, ? extends V> remappingFunction)

V merge(K key, V value,
  BiFunction<? super V, ? super V, ? extends V> remappingFunction)

Компараторы

@AllArgsConstructor
public class Person {
    @Getter
    private final String firstName;
    @Getter
    private final String lastName;
}
List<Person> people = new ArrayList<>();

Сортируем по фамилии

/*НЕПРАВИЛЬНО*/
Collections.sort(people,
 (p1, p2) ->
   p1.getLastName()
   .compareTo(
     p2.getLastName());
);
/*ПРАВИЛЬНО*/
Collections.sort(people,
  Comparator.comparing
   (Person::getLastName));
);

​Сортируем по фамилии, потом по имени​

//НЕ ПИШИТЕ ВЕСЬ ЭТОТ КОШМАР
Collections.sort(people,
  (p1, p2) -> {
    int result = p1.getFirstName().compareTo(p2.getFirstName());
    if (result == 0) {
      result = p2.getLastName().compareTo(p2.getLastName());
    }
    return result;
  }
);

Правильно так:

Collections.sort(people,
  Comparator
    .comparing(Person::getLastName)
    .thenComparing(Person::getFirstName));
);

Также:

Comparator.comparing(keyExtractor, keyComparator)
Comparator.comparingInt/Double(...)
Comparator.reversed()
Comparator.nullsFirst/nullsLast(Comparator c)
doctor

RDBMS — реляционные базы данных

  • Существуют с 1970-х годов.

  • Наиболее распространённый способ хранения информации в самых разнообразных системах.

  • Большое разнообразие зрелых, стабильных и богатых возможностями продуктов: IBM DB2, Oracle, MS SQL Server, Postgres, etc. etc.

  • Язык SQL стандартизован и слабо различается в разных системах.

  • В современных реалиях больших данных теснятся различными NoSQL системами, но пока ещё лидируют по распространённости

JDBC

  • "Java Database Connectivity"

  • Стандарт взаимодействия с реляционными СУБД

  • Назван по аналогии с ODBC (Open Database Connectivity) для языка C, разработанного Microsoft

  • Первая версия в 1996 году, текущая — 4.3 (2017-09-21)

  • Активно используется десятилетиями

Структура приложения

app

JDBC Drivers

  • Разрабатываются производителями БД или сообществами

  • У хороших БД — стабильные, высокого качества драйверы

Connection Strings aka Database URLs

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

jdbc:sqlserver://172.16.1.114:52836;databaseName=celesta

jdbc:postgresql://127.0.0.1:5432/celesta

jdbc:oracle:thin:192.168.110.128:1521:XE

jdbc:derby://localhost:1527/corejava;create=true

Создание Connection, Statement, получение и итерация по ResultSet

sequence

Получение данных и итерация

try (Connection conn = dataSource.getConnection();
     Statement stmt = conn.createStatement()){

  ResultSet result = stmt.executeQuery("select name from speaker");

  while (result.next()) {

      System.out.println(result.getString("name"));

  }
}

Как получить JDBC Connection

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

String connString = "jdbc:postgresql://127.0.0.1:5432/celesta"
Connection conn = DriverManager.getConnection(connString);

JDBC Connection: особенности

  • Корневой объект, «входная точка» ко всем возможностям взаимодействия с СУБД.

  • Медленно создаётся (связь по сети с СУБД, авторизация и т. д.).

  • Захватывает ресурсы: необходимо явно закрывать после использования.

  • Имеет тенденцию «портиться» («отваливаться»).

  • Не потокобезопасный. Нужен thread confinement.

  • Выход: connection pooling.

Connection Pool

pool

Connection Pool

  • Грамотно реализовать самостоятельно — сложно

  • Предоставляет ваш фреймворк / JDBC driver

  • Есть javax.sql.DataSource, являющийся стандартным интерфейсом для Connection Pool.

DataSource connectionPool = ...
try (Connection conn = connectionPool.getConnection()) {
    ...
}

Реализации Connection Pools (DataSource)

  • Apache Commons DBCP

  • Hikari CP

  • Другие фреймворки или JDBC-драйверы (например, H2)

Методы Connection

  • управление транзакциями

    • setAutoCommit

    • commit

    • rollback

  • создание statements:

    • createStatement() — создаёт универсальный объект для передачи SQL-команд

    • prepareStatement(String sql) — создаёт параметризованный шаблон SQL команды

    • prepareCall(String sql) — cоздаёт шаблон для вызова хранимой процедуры

Разновидности Statement

iostreams

Statement и PreparedStatement

int confId = ...
try (PreparedStatement stmt =
  conn.prepareStatement(
    "select name from talk where conferenceid = ?")) {
  //устанавливаем значение параметра
  //индексация с единицы!!
  stmt.setInt(1, confId);

  ResultSet result = stmt.executeQuery();
  while (result.next()) {
      System.out.println(result.getString("name"));
  }

}

SQL Injection

  • Запомним раз и навсегда: нельзя конструировать запросы в базу на основании данных от пользователя

НЕПРАВИЛЬНО

ПРАВИЛЬНО

stmt.executeQuery(
 "SELECT * FROM users " +
 "WHERE name = '" +
 userName + "'");
PreparedStatement stmt =
 conn.prepareStatement(
  "SELECT * FROM users WHERE name = ?");
stmt.setString(1, userName);
stmt.executeQuery();

SQL Injection

sql = "SELECT * FROM users WHERE name = '" + userName + "'"

userName = "' OR '1'='1"

userName = "a';DROP TABLE users;"

//... и много другой гадости

SQL Injection

sqlinjection

Демо-пример

  • Пишем базу данных выступлений на Java-конференциях

  • Основные сущности: на конференциях выступают докладчики с докладами.

  • Бывает, что один доклад делают несколько докладчиков одновременно.

ER model

systemtables

Наш учебный пример про доклады и спикеров

objects

Реальная «слоистая» архитектура серверного приложения

layers