Core Java. Лекция 4

instanceof и Pattern Matching. Класс Object. Исключения. Строки

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

Оператор 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 speak(); }

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

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

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

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

sealed interface Animal permits Dog, Cat { }

final class Dog implements Animal {

}
final class Cat implements Animal {

}

static String speak(Animal a) {
    return switch (a) {
        case Dog d -> "woof";
        case Cat c -> "meow";
    };
}

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

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

  • J. Bloch, 'Effective Java': 'By using a standard library, you take advantage of the knowledge of the experts who wrote it and the experience of those who used it before you…​ Numerous features are added to the libraries in every major release, and it pays to keep abreast of these additions.'

  • Наряду с актуальными, «свежими» классами в библиотеках есть много старых классов, оставленных для обратной совместимости.

  • Вы встретите Vector, Date, File, StringBuffer, Random, о которых я не буду рассказывать на лекциях.

  • Будьте внимательны: использование устаревших классов в существующем коде, в примерах в интернете и т. п. не оправдывает их использование в новом коде.

Отдельный пример из книги Effective Java: ThreadLocalRandom

//Плохо: сид выбирается при каждом вызове. НЕТ!!
double rnd = (new Random()).nextDouble();

//До Java 7+ так было нормально, но теперь НЕТ!!
static final Random r = new Random();
static double random() {
  return r.nextDouble();
}

//Java 7-16: в 3.6 раз быстрее, проще, качественнее
double rnd = ThreadLocalRandom.current().nextDouble();

//Java 17+: JEP356. Not thread safe!
RandomGenerator generator = RandomGenerator.getDefault();
generator.nextDouble();

Класс String

String e = ""; // an empty string
String java = "Java\u2122"; //Java™

Строка — иммутабельный объект.

Нужна другая строка? Сооружаем новую:

String greeting = "Hello!"
greeting = greeting.substring(0, 3) + "p!"; //Help!

Хотя внутри строки — массив, изменить его отдельные элементы нельзя!

Внутреннее представление строк

Пул строковых констант

String name1 = "John Doe";
String name2 = "John Doe";
/* DO NOT DO THIS!!*/
String name3
  = new String("John Doe");
stringpool

Сравнение строк

  • if (a == "John Doe") — неправильно, ошибка новичка.

  • if (a.equals("John Doe")) — плохо, получим NPE, если a == null.

  • if ("John Doe".equals(a)) — так делают серьёзные ребята.

  • if ("John Doe".equalsIgnoreCase(a)) — сравнение без учёта регистра.

  • if(str != null && !str.isEmpty()) — в этой строке что-то есть!

  • if(str != null && !str.isBlank()) — в этой строке что-то есть помимо пробелов, табуляций, переносов и проч.!

Интернирование строк

String name1 = "John Doe";
String name2 =
  new String("John Doe");
String name3 =
  new String("Jane Roe");
stringpool intern1

Интернирование строк

name1 = name1.intern();
name2 = name2.intern();
name3 = name3.intern();
stringpool intern2

Конкатенация строк

String foo = "foo";
String bar = "bar";

System.out.println(foo + bar); //foobar

Ситуация такова:

  • Пользовательской перегрузки операторов в Java не предусмотрено.

  • Оператор + для строк перегружен на уровне языка.

  • Оператор == для строк не перегружен, хоть его неперегруженная версия для строк и не имеет смысла :-(

​Конкатенация строк: проблема производительности​

//Плохая производительность, не делайте так!
String result = "";
for (int i = 0; i < numItems(); i++)
  result += lineForItem(i);
return result;

​Конкатенация строк: используйте StringBuilder!​

//Заранее можно аллоцировать приблизительное кол-во символов
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
//А можно и по умолчанию: new StringBuilder() (16 chars)
for (int i = 0; i < numItems(); i++)
  b.append(lineForItem(i));
return b.toString();

Длина строки

Длина строки в UTF-16 code units:

String greeting = "Hello";
int n = greeting.length(); // is 5.

«Реальная» длина строки в code points:

int cpCount = greeting.codePointCount(0, greeting.length());

Символ на i-й позиции

//работает за время O(1)
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o'

Если нужны code points:

int[] codePoints = str.codePoints().toArray();

Полезные методы

int length()
boolean isEmpty() / boolean isBlank()
char charAt(int index)
int compareTo(String anotherString)
boolean equals(Object anObject)
boolean equalsIgnoreCase(String anotherString)
boolean startsWith(String prefix)
boolean endsWith(String suffix)
String toLowerCase() / String toUpperCase()
String trim()
String join(CharSequence delimiter, CharSequence... elements)

Вредные методы

  • indexOf.. / lastIndexOf..

  • replace..

  • split..

В комбинации c substring и конкатенацией их используют для извлечения данных / преобразования строк.

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