Core Java. Лекция 4

Интерфейсы, enums, records. Оператор instanceof и Pattern Matching. Класс Object. Исключения

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

Интерфейсы

//его нельзя инстанцировать!
public interface Prism
{
   //это --- final-переменная!
   double PI = 3.14;

   //это --- публичные абстрактные методы!
   double getArea();
   double getHeight();

   //этот метод может вызывать другие и читать final-поля
   default double getVolume() {
      return getArea() * getHeight();
   }
}

Реализация интерфейса

public class Parallelepiped implements Prism {
    private double a;
    private double b;
    private double h;
    @Override
    public double getArea() {
        return a * b;
    }

    @Override
    public double getHeight() {
        return h;
    }
}

Если какой-то из методов интерфейса не будет переопределён, класс нужно пометить как abstract.

Чем интерфейсы отличаются от абстрактных классов?

  • Нет внутреннего состояния и конструкторов

  • Можно наследоваться (extends) только от одного класса, но реализовывать (implements) сколько угодно интерфейсов — множественное наследование.

  • Поэтому как абстракция, интерфейс предпочтительнее.

Специальные виды типов

  • Enumeration Classes

  • Records

  • Annotation Interfaces (о них речь пойдёт значительно позже)

Enumeration Classes (Java 5+)

public enum Size
  { SMALL, MEDIUM, LARGE, EXTRA_LARGE };

. . .

Size s = Size.MEDIUM;

for (Size s: Size.values()) . . .

switch (s) {
  case SMALL: . . .
  case LARGE: . . .
}

Поля, методы и конструкторы для перечислений

public enum Size
{
   SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
   private final String abbreviation;
   private Size(String abbreviation) {
     this.abbreviation = abbreviation;
   }
   public String getAbbreviation() {
     return abbreviation;
   }
}

. . .

Size s = . . .
s.getAbbreviation(); // вернёт S, M, L или XL

Enums: интерфейсы и абстрактные методы

interface Rule { boolean canGo();  }
enum TrafficLight implements Rule {
    RED {
        @Override public boolean canGo() { return false; }
        @Override int durationSeconds()  { return 55; }
    },
    YELLOW {
        @Override public boolean canGo() { return false; }
        @Override int durationSeconds()  { return 5; }
    },
    GREEN {
        @Override public boolean canGo() { return true; }
        @Override int durationSeconds()  { return 45; }
    };
    // Abstract method implemented individually by each constant
    abstract int durationSeconds();
}

Records (Java 16+)

  • Иммутабельные объекты (один раз создав, нельзя менять состояние)

  • Компактный синтаксис

  • Наследование запрещено, но можно реализовывать интерфейс

  • Автоматическая поддержка equals/hashCode (что это, мы узнаем вскоре)

  • Любой record может быть value-типом (value record).

//Defines a record with two int fields
public record Point(int x, int y) { }

Records

public record Point(int x, int y) {
    public double distance(Point other) {
        int dx = x - other.x;
        int dy = y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

Point p1 = new Point(1, 2);
//Get value via implicit accessor method
System.out.println(p1.x());
System.out.println(p1.distance(new Point(3, 4)));

Value-типы (Java28+ preview feature)

  • value-объект не имеет identity: два экземпляра с одинаковым состоянием неразличимы.

  • Все поля экземпляра неявно final; JVM может хранить объект без дополнительной косвенности.

  • value можно объявить обычный класс или record.

  • Конкретный value-класс неявно final; наследование возможно через abstract value class.

abstract value class Quantity {
  int value; //implicitly final
  Quantity(int value) {
    this.value = value;
  }
}
value class Distance //implicitly final
    extends Quantity {
  Distance(int value) {
    super(value);
  }
}
value record Point(int x, int y) {}

Базовые принципы проектирования классов

  • Минимизируйте область видимости (private всё, что только можно)

  • Минимизируйте мутабельность (final на всём, что только можно)

  • Документируйте точки расширения через наследование, или запрещайте наследование (final, sealed)

Оператор равенства ==

  • Для примитивов == сравнивает значения.

  • Для обычных объектов == сравнивает identity: ссылки должны указывать на один объект.

String a = "Java";
String b = new String("Java");
System.out.println(a == b); // false

Оператор равенства ==

  • Valhalla (Java 28, preview): у value-объектов нет identity. Для них == проверяет одинаковый класс и одинаковые значения полей.

var d1 = LocalDate.of(2026, 9, 29);
var d2 = d1.plusDays(1).minusDays(1);
System.out.println(d1 == d2); // true
  • «Быстрый» оператор == может оказаться не таким уж быстрым для больших value-объектов.

Оператор instanceof

instanceof
C1 c1; C2 c2; C3 c3; I1 i1;

x instanceof A //  false, если x == null
c1 instanceof С2 // true или false
i1 instanceof C2 // true или false
c2 instanceof C1 // всегда возвращает true
с3 instanceof C2 // не скомпилируется

Приведение типов в эпоху до Pattern Matching (Java 14+)

Person p = . . .;
if (p instanceof Student) {
    //если не защитить instanceof, возможен ClassCastException
    Student s = (Student) p;
    . . .
}

Pattern Matching for instanceof (Java 14+, JEP305)

Person p = . . .;
if (p instanceof Student s) {
   //здесь доступна переменная Student s
    . . .
} else {
   //здесь недоступна переменная Student s
}

//Скомпилируется
if (obj instanceof String s && s.length() > 5) {
   . . .
   s.contains(..)
}

//НЕ скомпилируется
if (obj instanceof String s || s.length() > 5) {...}

Pattern matching for switch (Java 21+)

public int calculate(Expr expr) {
  return switch (expr) {
    //Не скомпилируется, если мы забудем что-то из реализаций Expr!
    case Literal l -> l.value();
    case Divide d -> calculate(d.left()) / calculate(d.right());
    case Multiply m -> calculate(m.left()) * calculate(m.right());
    case Add a -> calculate(a.left()) + calculate(a.right());
    case Subtract s -> calculate(s.left()) - calculate(s.right());
  };
}

when guards for pattern matching

static String describe(Object o) {
  return switch (o) {
    case null -> "got null";

    case String s when s.isBlank() -> "blank string";
    case String s -> "string(" + s.length() + " chars)";
    case Integer i when i < 0 -> "negative int " + (-i);
    case Integer i            -> "int " + i;

    default -> "something else";
  };
}

Предупреждение про pattern matching

  • Pattern matching — это красивая возможность языка, но у неё ограничена область применения: Visitor Pattern, в основном нужный при разработке компиляторов / интерпретаторов формальных языков.

  • Не надо использовать pattern matching там, где достаточно обычного полиморфизма

Полиморфизм vs Pattern matching

  • Полиморфизм: поведение внутри класса

sealed interface Animal permits Dog, Cat { String vocalize(); }

final class Dog implements Animal {
    @Override public String vocalize() { return "woof"; }
}
final class Cat implements Animal {
    @Override public String vocalize() { return "meow"; }
}

static String vocalize(Animal a) {
    return a.vocalize();
}

Полиморфизм vs Pattern matching

  • Pattern matching: поведение вынесено из класса

sealed interface Animal permits Dog, Cat { }
final class Dog implements Animal {}
final class Cat implements Animal {}

static String inEnglish(Animal a) {
    return switch (a) {
        case Dog d -> "dog";
        case Cat c -> "cat";
    }; }
static String inRussian(Animal a) {
    return switch (a) {
        case Dog d -> "собачка";
        case Cat c -> "кошечка";
    }; }

Object: the Cosmic Superclass

  • Любой класс в Java является наследником Object

  • Писать class Employee extends Object не надо

  • В этом классе определены важные методы:

    • equals, hashCode, toString — рассмотрим прямо сейчас

    • notify, notifyAll, wait — рассмотрим в лекциях по Concurrency

    • getClass — в лекции про Reflection API

    • clone, finalize — не обращайте на них внимания, OK?

equals() и hashCode()

  • boolean equals(Object other) возвращает true т. и т. т., когда внутреннее состояние совпадает

  • int hashCode() возвращает целое значение, которое обязано совпадать для объектов с одинаковым внутренним состоянием

  • Это нужно для хеш-таблиц (и, пожалуй, является протекшей абстракцией)

Формальный контракт equals

  1. Рефлексивность:
    \(\forall x \ne \mathrm{null} (x.equals(x))\)

  2. Симметричность:
    \(\forall x \ne \mathrm{null} \, \forall y \ne \mathrm{null} (x.equals(y) \iff y.equals(x))\)

  3. Транзитивность:
    \(\forall x \ne \mathrm{null} \, \forall y \ne \mathrm{null} \, \forall z \ne \mathrm{null} (x.equals(y) \& y.equals(z) \Rightarrow x.equals(z))\)

  4. Консистентность: если сравниваемые объекты не изменялись, повторный вызов equals должен возвращать одно и то же значение.

  5. \(\forall x \ne \mathrm{null} (x.equals(\mathrm{null}) = \mathrm{false})\)

Формальный контракт hashCode

  1. Консистентность: если объект не изменялся, повторный вызов hashCode должен возвращать одно и то же значение (но не обязательно одно и то же между разными запусками приложения)

  2. Связь с equals:
    \(\forall x \forall y (x.equals(y) \Rightarrow x.hashCode() = y.hashCode())\)

  3. Хотя
    \(x.hashCode() = y.hashCode() \Rightarrow x.equals(y)\)
    и не обязательно, но желательно для большинства случаев.

Выводы

  1. Переопределять equals и hashCode нужно только вместе и согласованно, чтобы выполнить контракт \(x.equals(y) \Rightarrow x.hashCode() = y.hashCode()\)

  2. Грамотно реализовать equals и hashCode трудно, но, к счастью, самостоятельно это делать не нужно.

  3. Для тестирования есть специальная библиотка EqualsVerifier.

  4. Для генерации equals и hashCode можно использовать возможности IDE или библиотеки Lombok.

Генерация equals и hashCode

generateequals

Генерация equals и hashCode

public class Person {
    private String name;
    private int age;
    @Override
    public boolean equals(Object o) {
       // никогда, НИКОГДА не пишите это сами
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Или, если у нас Lombok

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Person {
    private  int age;
    private  String name;
}

Переопределение toString

public class Person {
    private String name;
    private int age;
    . . .
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
. . .
Person person = . . .
System.out.println(person);

Или, если у нас Lombok

import lombok.ToString;

@ToString
public class Person {
    private  int age;
    private  String name;
}

Полный «обвес» класса

public class Point {
  private final int x;
  private final int y;

  /*Подождите! Нам нужны:
   * конструктор
   * getX() и getY()
   * equals и hashCode
   * toString
   * 40 строчек кода из ничего!
   */

  public double distance(Point other) {
    ...
  }
}

Или, если у нас Lombok

import lombok.Data;

@Data
public class Point {
  private final int x;
  private final int y;
  public double distance(Point other) {
    ...
  }
}

Или, если у нас Java 14+

record Point(int x, int y) {

}
  • equals, hashCode и toString создаются неявно и автоматически

Исключительные ситуации

  • Программные ошибки (баги): null pointer dereference, выход за границы массива, некорректное приведение типов, деление на ноль и т. п.

  • Некорректные входные данные

  • Железо и сеть: невозможность открыть файл, нехватка памяти / места на диске и т. п.

Исключение — способ недвусмысленно сообщить о том, что вызов функции завершился неудачей (в отличие от return codes и т. п.).

Иерархия классов исключений

exceptionclasses

Что делаем?

  • Error и наследники — не обрабатываем (что-то катастрофическое случилось).

  • RuntimeException и наследники — их быть не должно (по идее, но не всё так просто).

  • Checked exceptions — их обрабатываем.

Декларирование checked-исключений

public FileInputStream(String name) throws FileNotFoundException{
  . . .
}

Декларирование более чем одного исключения

class MyAnimation {
. . .
  public Image loadImage(String s)
    throws FileNotFoundException, EOFException {
    . . .
  }
}
  • Не надо декларировать unchecked exceptions

  • Не надо декларировать исключения, если одно — подтип другого

  • Правда жизни: вам поможет IDE

Выбрасывание исключений

throw new EOFException();

(Если мы выбрасываем checked exception, компилятор позволит это сделать либо если оно задекларировано, либо если оно обрабатывается в самом методе.)

Выбрасывание исключений

Не следует выбрасывать явно

  • Exception

  • RuntimeException

  • Throwable

  • Error

(J. Bloch, Effective Java, Item 72)

Откуда наследовать своё собственное исключение? Вот в чём вопрос

  • Если наследовать от Exception — надо быть готовым, что его придётся декларировать или ловить всюду. Хорошо это или плохо?

  • Можно унаследоваться от RuntimeException. И забыть его поймать.

  • В эпоху лямбд и стримов checked exceptions это скорее головная боль.

  • Споры не прекращаются.

Как поймать исключение: try-catch блок

try {
  // . . . code . . .
  // . . . more code . . .
  // . . . more code . . .
} catch (ExceptionType e) {
  // handler for this type of exception
  // use e object to extract the data!
}

Как поймать несколько типов исключений

iomultiple
try {
  //code that might throw exceptions
} catch (FileNotFoundException e) {
  //emergency action for missing files
} catch (UnknownHostException e) {
  // emergency action for unknown hosts
} catch (IOException e) {
  // emergency action for all other I/O problems
}

try-multicatch

iomultiple
try {
  //code that might throw exceptions
} catch (FileNotFoundException | UnknownHostException e) {
  //emergency action for missing files or unknown hosts

  //e type is the most specific common supertype
  //of the throwable types
}

Перевыбрасывание исключений (с умной проверкой типов)

. . . throws SQLException . . .

try {
  //доступ к БД -- может выбросить SQLException
} catch (Exception e) {
  logger.log(level, message, e);
  //хотя Exception есть тип более широкий,
  //чем задекларирован в методе, компилятор поймёт,
  //что всё ок
  throw e;
}

Обёртывание исключений

. . . throws ServletException . . .

try {
  //доступ к БД -- может выбросить SQLException
} catch (SQLException e) {
  ServletException se = new ServletException("database error");
  //сохраняем информацию о первопричине
  se.initCause(e);
  throw se;
}

Переопределение методов с исключениями

overriding
  • Исключение может быть того же типа или субтипа

  • Не возбраняется, если его не будет вообще

Стандартные типы переиспользуемых unchecked-exceptions

J. Bloch 'Effective Java', Item 72:

InvalidArgumentException

неправильный, ненулевой параметр метода

InvalidStateException

внутреннее состояние объекта не подходит для запуска метода

NullPointerException

передан null в метод, не ожидающий null в качестве параметра

IndexOutOfBoundsException

Индексный параметр выходит за допустимый диапазон

Вам будет очень хотеться сделать так…​

try {
 ...
} catch (Exception e) {
 e.printStackTrace();
 //И компилятор счастлив!
 //(но коллега во время код-ревью -- нет)
}

…​но так делать не надо!

Если не понятно, что делать с исключением

  • Задекларируйте checked exception в методе

  • Оберните с помощью initCause или параметра конструктора

    • в задекларированный checked exception

    • в unchecked exception (InvalidStateException, например)

  • Лучше не надо: Lobmok’s @SneakyThrows (самая спорная фича Lombok)

Общее правило

  • Throw early, catch late.

  • Не откладывайте выбрасывание исключения, как только стала ясна причина ошибки.

  • Не спешите обрабатывать исключение, пока вам не стал полностью ясен механизм обработки.

finally-блок

InputStream in = new FileInputStream(. . .);
try {
  // исключение может возникнуть здесь
  code that might throw exceptions
  // и даже возврат из метода вызовет блок finally!
  if (...)
    return;
} catch (IOException e) {
  // бывает, исключение возникает во время
  // обработки исключений
  show error message
} finally {
  // в любом случае сработает finally-блок!
  in.close();
}

Беда с finally-block

...throws IOException...
//Один ресурс

BufferedReader br = new BufferedReader(new FileReader(path));

try {return br.readLine();}

finally {br.close();}

Беда с finally-block

...throws IOException...
//Два ресурса

InputStream in = new FileInputStream(src);
try {
  OutputStream out = new FileOutputStream(dst);
  try {
    byte[] buf = new byte[BUFFER_SIZE];
    int n;
    while ((n = in.read(buf)) >= 0)out.write(buf, 0, n);}
  finally {out.close();}}
finally {in.close();}

try-with-resources-блок

Общая схема

try (Resource res = . . .) {
  work with res
}

Пример:

try (Scanner in = new Scanner(
    new FileInputStream("/usr/share/dict/words")), "UTF-8") {
  while (in.hasNext())
    System.out.println(in.next());
}

Множество ресурсов

try (Scanner in = new Scanner(
     new FileInputStream("/usr/share/dict/words"), "UTF-8");
     PrintWriter out = new PrintWriter("out.txt")) {
  while (in.hasNext())
    out.println(in.next().toUpperCase());
}

Интерфейсы Closeable и AutoCloseable

closeable

Исключения — для исключительных случаев!

//ЧУДОВИЩНО. НЕ ДЕЛАЙТЕ ТАК
try {
  int i = 0;
  while (true)
    range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

//ДЕЛАЙТЕ ТАК!!
for (Mountain m: range)
  m.climb();

Исключения — для исключительных случаев!

//ЧУДОВИЩНО. НЕ ДЕЛАЙТЕ ТАК
try {
  Iterator<Foo> i = collection.iterator();
  while (true)
    Foo foo = i.next();
} catch (NoSuchElementException e) {
}

//ДЕЛАЙТЕ ТАК!!
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
    Foo foo = i.next();
    . . .
}

Не используйте исключения для контроля выполнения

  • Это маскирует настоящие ошибки и делает код трудным для поддержки.

  • Это затратно по ресурсам (исключения несут в себе Stack Trace).

  • Это медленно: компилятор не оптимизирует под исключения.

Исключение вылезло в production. Что делать?

 2019-08-24 11:14:55.545 ERROR 30413 --- [0.1-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
 java.lang.NullPointerException: null
at guess.service.AnswerServiceImpl.setAnswer(AnswerServiceImpl.java:37) ~[classes!/:na]
at guess.controller.AnswerController.addAnswer(AnswerController.java:31) ~[classes!/:na]
at sun.reflect.GeneratedMethodAccessor75.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_222]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_222]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.21.jar!/:9.0.21]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_222]

Analyze → Analyze Stack Trace…​

analyzestacktrace

Исследуем стек вызовов

stacktracereading