Core Java. Лекция 4
Enumerations. Исключения. Строки
Иван Пономарёв, КУРС/МФТИ
Enumeration Classes
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();
Базовые принципы проектирования классов
Минимизируйте область видимости (private
всё, что только можно)
Минимизируйте мутабельность (final
на всём, что только можно)
Документируйте точки расширения через наследование, или запрещайте наследование (final
)
Исключительные ситуации
Программные ошибки (баги): null pointer dereference, выход за границы массива, некорректное приведение типов, деление на ноль и т. п.
Некорректные входные данные
Железо и сеть: невозможность открыть файл, нехватка памяти / места на диске и т. п.
Исключение — способ недвусмысленно сообщить о том, что вызов функции завершился неудачей (в отличие от 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 {
} catch (ExceptionType e) {
}
Как поймать несколько типов исключений
try {
} catch (FileNotFoundException e) {
} catch (UnknownHostException e) {
} catch (IOException e) {
}
try-multicatch
try {
} catch (FileNotFoundException | UnknownHostException e) {
}
Перевыбрасывание исключений (с умной проверкой типов)
. . . throws SQLException . . .
try {
} catch (Exception e) {
logger.log(level, message, e);
throw e;
}
Обёртывание исключений
. . . throws ServletException . . .
try {
} catch (SQLException e) {
ServletException se = new ServletException("database error");
se.initCause(e);
throw se;
}
Переопределение методов с исключениями
Стандартные типы переиспользуемых unchecked-exceptions
J. Bloch 'Effective Java', Item 72:
| неправильный, ненулевой параметр метода |
| внутреннее состояние объекта не подходит для запуска метода |
| передан null в метод, не ожидающий null в качестве параметра |
IndexOutOfBoundsException
| Индексный параметр выходит за допустимый диапазон |
Вам будет очень хотеться сделать так…
try {
...
} catch (Exception e) {
e.printStackTrace();
}
Если не понятно, что делать с исключением
Задекларируйте checked exception в методе
Оберните с помощью initCause
или параметра конструктора
Лучше не надо: Lobmok’s @SneakyThrows
(самая спорная фича Lombok)
Общее правило
Throw early, catch late.
Не откладывайте выбрасывание исключения, как только стала ясна причина ошибки.
Не спешите обрабатывать исключение, пока вам не стал полностью ясен механизм обработки.
finally-блок
InputStream in = new FileInputStream(. . .);
try {
code that might throw exceptions
if (...)
return;
} catch (IOException e) {
show error message
} 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
Исключения — для исключительных случаев!
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…
Здесь мы делаем паузу в изучении особенностей языка ради изучения библиотеки
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();
static final Random r = new Random();
static double random() {
return r.nextDouble();
}
double rnd = ThreadLocalRandom.current().nextDouble();
RandomGenerator generator = RandomGenerator.getDefault();
generator.nextDouble();
Класс String
String e = "";
String java = "Java\u2122";
Строка — иммутабельный объект.
Нужна другая строка? Сооружаем новую:
String greeting = "Hello!"
greeting = greeting.substring(0, 3) + "p!";
Хотя внутри строки — массив, изменить его отдельные элементы нельзя!
Внутреннее представление строк
Пул строковых констант
String name1 = "John Doe";
String name2 = "John Doe";
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);
Пользовательской перегрузки операторов в Java не предусмотрено.
Оператор +
для строк перегружен на уровне языка.
Оператор ==
для строк не перегружен, хоть его неперегруженная версия для строк и не имеет смысла :-(
​Конкатенация строк: проблема производительности​
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i);
return result;
​Конкатенация строк: используйте StringBuilder!​
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
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();
«Реальная» длина строки в code points:
int cpCount = greeting.codePointCount(0, greeting.length());
Символ на i-й позиции
char first = greeting.charAt(0);
char last = greeting.charAt(4);
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)
Вредные методы
В комбинации c substring
и конкатенацией их используют для извлечения данных / преобразования строк.
Не делайте так. Это путь к бесконечным багам, бесконечной боли и бесконечному стыду. Используйте конечные автоматы или регулярные выражения.