Core Java. Lecture #4

Enumerations. Exceptions. Strings

Ivan Ponomarev, Synthesized.io/MIPT

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: . . .
}

Fields, methods and constructors for enum classes

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(); // returns S, M, L или XL

Basic principles of class design

  • Minimize the scope (use private wherever you can)

  • Minimize mutability (use final wherever you can)

  • Document extension points or prohibit inheritance (final or Javadoc on methods)

Exceptions

  • Program errors (bugs): null pointer dereference, running out of array bounds, invalid typecasts, division by zero etc.

  • Invalid input, user’s errors

  • Hardware and network: cannot open a file, running out of memory / out of disk space etc.

Exception is an unambiguous way to tell the user that the call was finished unsuccessfully (unlike return codes etc.).

Exception classes hierarchy

exceptionclasses

What should we do when we got an exception?

  • Error and sublasses — don’t handle (something catastrophic happened).

  • RuntimeException and sublasses — fix the code, there should be none! (in fact, it’s more complex than that).

  • Checked exceptions — handle them.

Declaring checked exceptions

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

Declaring more than one exception

class MyAnimation {
. . .
  public Image loadImage(String s)
    throws FileNotFoundException, EOFException {
    . . .
  }
}
  • No need to declare unchecked exceptions

  • No need to declare two exceptions, if one is a sublass of the othern

  • Truth of Life: IDE does everything for you

Throwing exceptions

throw new EOFException();

(If we throw a checked exception, the compiler will let us do it only if it’s either declared or handled in the same method.)

Throwing exceptions

We should not explicitly throw

  • Exception

  • RuntimeException

  • Throwable

  • Error

(J. Bloch, Effective Java, Item 72)

What base class should we choose for our exception? This is the question

  • If we inherit it from Exception — we should be ready to either declare or catch it everywhere. Is it good or bad?

  • We can inherit from RuntimeException. And forget to catch it.

  • In the age of lambdas and streams, checked exceptions are rather an unneeded headache.

  • People are still arguing about checked exceptions.

How to catch an exception: the try-catch block

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

How to catch several types of exceptions

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
}

Rethrowing exceptions (with smart type check)

. . . throws SQLException . . .

try {
  //database access: only SQLException can be thrown here
} catch (Exception e) {
  logger.log(level, message, e);
  //Although Exception is a less specific than SQLException
  //the compiler will be ok with this
  throw e;
}

Wrapping exceptions

. . . throws ServletException . . .

try {
  //database access: SQLException can be thrown here
} catch (SQLException e) {
  ServletException se = new ServletException("database error");
  //we save information about the initial cause
  se.initCause(e);
  throw se;
}

Overriding methods with declared exceptions

overriding
  • Exception can be of the same type or subtype

  • It is not prohibited if there will be no exception at all

Standard reusable types of unchecked exceptions

J. Bloch 'Effective Java', Item 72:

InvalidArgumentException

invalid non-null method argument

InvalidStateException

internal state of the object doesn’t fit for the invocation of this method

NullPointerException

null reference is passed to the method that does not expect `null`s in its arguments

IndexOutOfBoundsException

index is either less than minimum value or bigger than maximum value

You will be tempted to do this…​

try {
 ...
} catch (Exception e) {
 e.printStackTrace();
 //And compiler is happy!
 //(but your colleague who does the code review is not)
}

…​but you shoud not do this!

If you can’t understand what to do with the exception

  • Declare checked exception in the method

  • Wrap using initCause or constructor argument

    • into the declared checked exception

    • into unchecked exception (InvalidStateException, for example)

  • Better don’t do this: Lobmok’s @SneakyThrows (the most arguable feature of Lombok)

The general rule

  • Throw early, catch late.

  • Throw an exception as soon as it is clear that something is wrong.

  • Do not handle an exception until it is clear how exactly you should do it.

finally block

InputStream in = new FileInputStream(. . .);
try {
  // an exception can be thrown here
  code that might throw exceptions
  // and even return from the method will call the 'finally' block!
  if (...)
    return;
} catch (IOException e) {
  // sometimes an exception is thrown
  // during exception handling
  show error message
} finally {
  // finally блок will work in any case!
  in.close();
}

A problem with finally block

...throws IOException...
//One resource

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

try {return br.readLine();}

finally {br.close();}

A problem with finally block

...throws IOException...
//Two resources

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

Common scheme

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

Example:

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

Multiple resources

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 and AutoCloseable interfaces

closeable

Exceptions are for exceptional cases!

//HORRIBLE. DON'T DO THIS
try {
  int i = 0;
  while (true)
    range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

//DO THIS!!
for (Mountain m: range)
  m.climb();

Exceptions are for exceptional cases!

//HORRIBLE. DON'T DO THIS
try {
  Iterator<Foo> i = collection.iterator();
  while (true)
    Foo foo = i.next();
} catch (NoSuchElementException e) {
}

//DO THIS!!
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
    Foo foo = i.next();
    . . .
}

Don’t use exceptions for control flow

  • It masks the real errors and makes the code difficult to support.

  • It is resource consuming (exceptions have huge stack traces attached).

  • It is slow: compiler never optimizes for exceptions.

Exception is in production. What should we do?

 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

Expore the stack trace

stacktracereading

Here we make a pause in learning the language features in favor of learning the library

  • 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.'

  • Besides "fresh" classes the snandard library still holds many outdated classes for the sake of backwards compatibility.

  • You will meet Vector, Date, File, StringBuffer, Random and others which I will not describe in my lectures.

  • Be careful: the fact that you saw them in legacy code, some outdated tutorial on Internet etc. does not justify their usage in the new code

A specific example from 'Effective Java' book: ThreadLocalRandom

//Bad: seed is calculated at each invocation. DON'T!!
double rnd = (new Random()).nextDouble();

//Before Java 7+ it was OK, but now DON'T!!
static final Random r = new Random();
static double random() {
  return r.nextDouble();
}

//Java 7-16: 3.6 times faster, statistically better
double rnd = ThreadLocalRandom.current().nextDouble();

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

String class

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

String is an immutable object.

Need another string? Build another one from scratch:

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

Although there is an array inside the String, you cannot change its elements!

Internal representation of strings

Internal String constants pool

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

Comparing strings

  • if (a == "John Doe") — wrong, novice’s error.

  • if (a.equals("John Doe")) — wrong, we’ll get NPE, if a == null.

  • if ("John Doe".equals(a)) — this is how experienced folks do it.

  • if ("John Doe".equalsIgnoreCase(a)) — case insensitive comparision.

  • if(str != null && !str.isEmpty()) — there is something in this string!

  • if(str != null && !str.isBlank()) — there is something in this string besides spaces, tabs, carriage returns etc.!

String interning

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

String interning

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

String concatenation

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

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

The situation is following:

  • Unlike other languages, there is no operators overloading for users in Java.

  • + operator is overloaded for strings on language level.

  • == operator is not overloaded, although its non-overloaded version does not make sense :-(

String concatenation: performance problem

//Don't do this: the performance is poor!
String result = "";
for (int i = 0; i < numItems(); i++)
  result += lineForItem(i);
return result;

String concatenation: use StringBuilder!

//You can allocate an approximate number of characters in advance
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
//And you can always use the default: new StringBuilder() (16 chars)
for (int i = 0; i < numItems(); i++)
  b.append(lineForItem(i));
return b.toString();

String length

String length in UTF-16 code units:

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

«The real» string length in code points:

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

Character at a given position

//works at O(1) time
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o'

If you need code points:

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

Useful methods

int length()
char charAt(int index)
int compareTo(String other)
boolean equals(Object other)
boolean  equalsIgnoreCase(Object other)
boolean startWith(String prefix)
boolean endsWith(String suffix)
String toLowerCase() / String toUpperCase()
String trim()
String join(CharSequence delimiter, CharSequence... elements)

Harmful methods

  • indexOf.. / lastIndexOf..

  • replace..

  • split..

In combination with substring and concatenation they are sometimes used for string parsing/transformation.

Don’t do that. This is the way for endless pain, endless bugs and endless shame. Use definite state machines or regular expressions instead.