«Подлые броски» на Java
В Java концепция мгновенного броска позволяет нам генерировать любое проверенное исключение, не определяя его явно в сигнатуре метода. Это позволяет опустить объявление throws , эффективно имитируя характеристики исключения времени выполнения.
В этой статье мы увидим, как это делается на практике, рассмотрев несколько примеров кода.
2. О скрытых бросках
Проверенные исключения являются частью Java, а не JVM. В байт-коде мы можем кидать любое исключение откуда угодно, без ограничений.
В Java 8 появилось новое правило вывода типов, которое гласит, что throws T выводится как RuntimeException всякий раз, когда это разрешено. Это дает возможность реализовать скрытые броски без вспомогательного метода.
Проблема скрытых бросков заключается в том, что вы, вероятно, захотите перехватывать исключения в конце концов, но компилятор Java не позволяет вам перехватывать скрытно генерируемые проверенные исключения, используя обработчик исключений для их конкретного типа исключения.
3. Подлые броски в действии
Как мы уже упоминали, компилятор и среда выполнения Jave могут видеть разные вещи:
public static E extends Throwable> void sneakyThrow(Throwable e) throws E throw (E) e; > private static void throwSneakyIOException() sneakyThrow(new IOException("sneaky")); >
Компилятор видит сигнатуру с бросками T , относящимися к типу RuntimeException , поэтому он разрешает распространение непроверенного исключения. Среда выполнения Java не видит никакого типа в бросках, поскольку все броски одинаковы: простой бросок e .
Этот быстрый тест демонстрирует сценарий:
@Test public void throwSneakyIOException_IOExceptionShouldBeThrown() assertThatThrownBy(() -> throwSneakyIOException()) .isInstanceOf(IOException.class) .hasMessage("sneaky") .hasStackTraceContaining("SneakyThrowsExamples.throwSneakyIOException"); >
Кроме того, можно сгенерировать проверенное исключение, используя манипуляции с байт-кодом или Thread.stop(Throwable) , но это запутанно и не рекомендуется.
4. Использование аннотаций Ломбока
Аннотация @SneakyThrows от Lombok позволяет вам создавать проверенные исключения без использования объявления throws . Это удобно, когда вам нужно вызвать исключение из метода в очень ограниченных интерфейсах, таких как Runnable.
Скажем, мы выбрасываем исключение из Runnable ; он будет передан только обработчику необработанных исключений потока .
Этот код вызовет экземпляр Exception , поэтому вам не нужно оборачивать его в RuntimeException:
@SneakyThrows public static void throwSneakyIOExceptionUsingLombok() throw new IOException("lombok sneaky"); >
Недостатком этого кода является то, что вы не можете перехватить проверенное исключение, которое не объявлено. Например, если мы попытаемся поймать IOException , скрытно созданный описанным выше методом, мы получим ошибку компиляции.
Теперь давайте вызовем throwSneakyIOExceptionUsingLombok и ожидаем, что Lombok выдаст исключение IOException:
@Test public void throwSneakyIOExceptionUsingLombok_IOExceptionShouldBeThrown() assertThatThrownBy(() -> throwSneakyIOExceptionUsingLombok()) .isInstanceOf(IOException.class) .hasMessage("lombok sneaky") .hasStackTraceContaining("SneakyThrowsExamples.throwSneakyIOExceptionUsingLombok"); >
5. Вывод
Как мы видели в этой статье, мы можем обмануть компилятор Java, чтобы обрабатывать проверенные исключения как непроверенные.
Как всегда, код доступен на GitHub .
@SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
Overview
@SneakyThrows can be used to sneakily throw checked exceptions without actually declaring this in your method’s throws clause. This somewhat contentious ability should be used carefully, of course. The code generated by lombok will not ignore, wrap, replace, or otherwise modify the thrown checked exception; it simply fakes out the compiler. On the JVM (class file) level, all exceptions, checked or not, can be thrown regardless of the throws clause of your methods, which is why this works.
- A needlessly strict interface, such as Runnable — whatever exception propagates out of your run() method, checked or not, it will be passed to the Thread ‘s unhandled exception handler. Catching a checked exception and wrapping it in some sort of RuntimeException is only obscuring the real cause of the issue.
- An ‘impossible’ exception. For example, new String(someByteArray, «UTF-8»); declares that it can throw an UnsupportedEncodingException but according to the JVM specification, UTF-8 must always be available. An UnsupportedEncodingException here is about as likely as a ClassNotFoundError when you use a String object, and you don’t catch those either!
Being constrained by needlessly strict interfaces is particularly common when using lambda syntax ( arg -> action ); however, lambdas cannot be annotated, which means it is not so easy to use @SneakyThrows in combination with lambdas.
Be aware that it is impossible to catch sneakily thrown checked types directly, as javac will not let you write a catch block for an exception type that no method call in the try body declares as thrown. This problem is not relevant in either of the use cases listed above, so let this serve as a warning that you should not use the @SneakyThrows mechanism without some deliberation!
You can pass any number of exceptions to the @SneakyThrows annotation. If you pass no exceptions, you may throw any exception sneakily.
With Lombok
| import lombok.SneakyThrows;
public class SneakyThrowsExample implements Runnable < @SneakyThrows |
Vanilla Java
| import lombok.Lombok;
public class SneakyThrowsExample implements Runnable < public void run () < |
Supported configuration keys:
lombok.sneakyThrows.flagUsage = [ warning | error ] (default: not set) Lombok will flag any usage of @SneakyThrows as a warning or error if configured.
Small print
Because @SneakyThrows is an implementation detail and not part of your method signature, it is an error if you try to declare a checked exception as sneakily thrown when you don’t call any methods that throw this exception. (Doing so is perfectly legal for throws statements to accommodate subclasses). Similarly, @SneakyThrows does not inherit.
For the nay-sayers in the crowd: Out of the box, Eclipse will offer a ‘quick-fix’ for uncaught exceptions that wraps the offending statement in a try/catch block with just e.printStackTrace() in the catch block. This is so spectacularly non-productive compared to just sneakily throwing the exception onwards, that Roel and Reinier feel more than justified in claiming that the checked exception system is far from perfect, and thus an opt-out mechanism is warranted.
If you put @SneakyThrows on a constructor, any call to a sibling or super constructor is excluded from the @SneakyThrows treatment. This is a java restriction we cannot work around: Calls to sibling/super constructors MUST be the first statement in the constructor; they cannot be placed inside try/catch blocks.
@SneakyThrows on an empty method, or a constructor that is empty or only has a call to a sibling / super constructor results in no try/catch block and a warning.
Java 15 глазами программиста Scala
Lombok не манипулирует байткодом. Библиотека выступает процессором аннотаций, который на соответствующем шаге вызывает компилятор для генерации обычного java-кода.
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
А как же @SneakyThrows?
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
А что с ним? Это просто генерация try-catch вокруг тела аннотированного метода.
Комментарий пока не оценивали 0
Ответить Добавить в закладки Ещё
Показать предыдущий комментарий
Да, sneaky throws основан на такой фишке JVM, что когда мы бросаем generic exception (который может быть как checked, так и unchecked) — JVM не вправе рассматривать exception как checked во всех случаях, т.к. exception можеть быть unchecked.
По сути работает такая конструкция:
static void throwQuietly(Exception e) throws E
Использовать можно так:
try < smth that throws checked exception >catch(Exception e)
В случае экспешена он будет выброшен, но обрабатывать его не нужно, даже если он checked.
Core Java. Лекция 4
Исключение — способ недвусмысленно сообщить о том, что вызов функции завершился неудачей (в отличие от return codes и т. п.).
Иерархия классов исключений
Что делаем?
- 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! >
Как поймать несколько типов исключений

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

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; >
Переопределение методов с исключениями

- Исключение может быть того же типа или субтипа
- Не возбраняется, если его не будет вообще
Стандартные типы переиспользуемых unchecked-exceptions
J. Bloch ‘Effective Java’, Item 72:
неправильный, ненулевой параметр метода
внутреннее состояние объекта не подходит для запуска метода
передан null в метод, не ожидающий null в качестве параметра
Индексный параметр выходит за допустимый диапазон
Вам будет очень хотеться сделать так…
try < . >catch (Exception e) < e.printStackTrace(); //И компилятор счастлив! //(но коллега во время код-ревью -- нет) >
…но так делать не надо!
Если не понятно, что делать с исключением
- Задекларируйте checked exception в методе
- Оберните с помощью initCause или параметра конструктора
- в задекларированный checked exception
- в unchecked exception ( InvalidStateException , например)
Общее правило
- 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
Беда с 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 > finally
try-with-resources-блок
try (Resource 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

Исключения — для исключительных случаев!
//ЧУДОВИЩНО. НЕ ДЕЛАЙТЕ ТАК try < int i = 0; while (true) range[i++].climb(); > catch (ArrayIndexOutOfBoundsException e) < >//ДЕЛАЙТЕ ТАК!! for (Mountain m: range) m.climb();
Исключения — для исключительных случаев!
//ЧУДОВИЩНО. НЕ ДЕЛАЙТЕ ТАК try < Iterator i = collection.iterator(); while (true) Foo foo = i.next(); > catch (NoSuchElementException e) < >//ДЕЛАЙТЕ ТАК!! for (Iterator i = collection.iterator(); i.hasNext(); )
Не используйте исключения для контроля выполнения
- Это маскирует настоящие ошибки и делает код трудным для поддержки.
- Это затратно по ресурсам (исключения несут в себе 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…

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

Здесь мы делаем паузу в изучении особенностей языка ради изучения библиотеки
- 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!
Хотя внутри строки — массив, изменить его отдельные элементы нельзя!
Внутреннее представление строк
- До Java 9 — char[]
- После Java 9 — byte[] и byte coder
- UTF-16 / Latin1 (‘Compact Strings’)
- Алексей Шипилёв — The Lord of the Strings: Two Scours
Пул строковых констант
String name1 = "John Doe"; String name2 = "John Doe"; /* DO NOT DO THIS!!*/ String name3 = new String("John Doe");

Сравнение строк
- 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");

Интернирование строк
name1 = name1.intern(); name2 = name2.intern(); name3 = name3.intern();

Конкатенация строк
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 и конкатенацией их используют для извлечения данных / преобразования строк.
Не делайте так. Это путь к бесконечным багам, бесконечной боли и бесконечному стыду. Используйте конечные автоматы или регулярные выражения.
