Internationalisation (I18N)
The Date and Time API
In Java, an Instant
represents a point on the time line. The origin (called epoch) is arbitrarily set at midnight
of the first of January 1970 Greenwich time (UTC). Instant.now()
returns the current point on the time line,
being the number of seconds since the epoch (stored as long
) and the number of nano seconds (stored as int
). Each
day has 24x60x60 = 86400 seconds. A Duration
is the amount of time between two instants. Both Instant
and Duration
are immutable.
An Instant
does not have a human readable format. The two kinds of human time in the java API are local date/time and
zoned time. Local date/time has a date and/or time of day, but no associated time zone. Examples:
Local Date example. Local Time Example.
Do not use zoned time, unless really necessary. Today’s date can be retrieved using LocalDate.now()
, an arbitrary date
with LocalDate.of(<year>, <month>, <day>)
like LocalDate.of(2021, 5, 1)
being first of May 2021. The amount of time
between dates is called a Period
. Check the java API to read about convenient methods of LocalDate
and Period
. An
interesting one is the datesUntil(…)
method on LocalDate
, which returns a Stream<LocalDate>
.
LocalTime
objects can be created like LocalDates, e.g.: LocalTime endOfLecture = LocalTime.now().plusHours(1);
Zoned times make use of time zomes. Java uses the IANA database: https://www.iana.org/time-zones
To make a zoned time out of a LocalDateTime
you can use the ZonedDateTime.of()
method or do it like this:
ZonedDateTime current = LocalDateTime.now().atZone(ZoneId.of("Europe/Amsterdam"));
. Now you get a specific Instant
in time.
The other way around, you can can find the corresponding ZonedDateTime
in another time zone using instant.atZone(…)
.
To represent dates and times in readable and customizable formats, use the DateTimeFormatter
class. It can use predefined
standard formatters, Locale
specific formatters and formatters with custom patterns.
This paragraph is based on Core Java Volume 2, 11th edition, chapter 6 by Cay Horstmann.
Number Formats
Some of you might have noticed that one of the teacher tests in the FXTriangulate exercise fails on your machine.
This is because on the machine we developed, the locale is set to en_US.
This causes the numbers to be formatted in the way that Double.parseDouble(String input)
expects it.
If you run the same tests on a machine with say a German Locale, the test that reads the length back from the label,
use Double.parseDouble(), which is then surprised to find a comma instead of the decimal point, and fails with a format exception.
To solve that, modify the test in method tLength such that the linedouble l = Double.parseDouble( ltext.split( ":" )[ 1 ] );
readsdouble l = getDoubleConsideringLocale( ltext.split( ":" )[ 1 ] );
,
so that the whole test method reads
Testing Localized Exceptions
The standard way of testing exceptions with assertj is explained in week01.
To get to the localized message, which contains the message as translated by the locale framework is a bit more involved.
Luckily, AssertJ allows you to extract information from a Throwable, by using an extractor function. Now the Lambda bells should ring.
If you turn on type hints in NetBeans-IDE (or in intelij) you can see what the type is on which you call contains(keys)
image::assertjtypehints.png
Additional Pointers
- If you haven’t read the Horstmann book but you need an introduction into Internationalization, read this tutorial from DZONE here. Make sure to read the bit about Resource Bundles, as you use them in the exercise for this week.
- Jakob Jenkov also has a tutorial on Java Internationalization
Exceptions Summary
Exception-Handling
- Throw statement (cfr.
throw new YourFavoriteException()
) try
block: the code that is executed in normal circumstances, but might throw an exception.catch
block: the code that is executed to handle an exception that was thrown in the try block. The so called catch–block-parameter specifies which exception type can be caught by thecatch
block.- Examples:
Exception types
- System errors are thrown by JVM and represented in the
Error
class. TheError
class describes internal system errors. Such errors rarely occur. If one does, there is little you can do beyond notifying the user and trying to terminate the program gracefully. Errors are unchecked exceptions. However, often you are too late and the JVM will take over or might crash itself. Exception
describes errors caused by your program and external circumstances. These errors can be caught and handled by your program.RuntimeException
is caused by programming errors, such as bad casting, accessing an out-of-bounds array, and numeric errors. Typically the programmer (maybe you) is to blame.
Checked Exceptions vs. Unchecked Exceptions
RuntimeException
,Error
and their subclasses are known as unchecked exceptions. All other exceptions are known as checked exceptions, meaning that the compiler forces the programmer to check and deal with the exceptions.- In most cases, unchecked exceptions reflect programming logic errors that are not recoverable. For example:
- A
NullPointerException
is thrown if you access an object through a reference variable before an object is assigned to it; - an
IndexOutOfBoundsException
is thrown if you access an element in an array outside the bounds of the array.
- A
- Unchecked exceptions can occur anywhere in the program. To avoid cumbersome overuse of try-catch blocks, Java does not mandate you to write code to catch unchecked exceptions.
- Often runtime exceptions point to a logic fault in the program, such as forgetting a nullity check before dereferencing a reference result from a method, or violating a precondition.
- Null values often indicate that an expected value could not be produced, such as in a search. If you are designing an API with such search, better return an empty collection (List, Set, Map) instead. Empty collections are predefined and there for cheap to return. Also, since Java 8, the preferred return
type for some method that may of may not produce a result is
java.util.Optional<T>
. Usage explanation in Guide to Java 8 Optional on Baeldung.
- Null values often indicate that an expected value could not be produced, such as in a search. If you are designing an API with such search, better return an empty collection (List, Set, Map) instead. Empty collections are predefined and there for cheap to return. Also, since Java 8, the preferred return
type for some method that may of may not produce a result is
More on exception handling and finally
clause
- The code in the optional
finally
block is executed under all circumstances, regardless of whether an exception occurs in the try block or is caught. The code is called before the method is left.
Custom Exception
classes
You can roll your own exceptions, in case your "business" has requirements that demand this.
Try with resources.
File
is one of the classes that has operations (methods) that throw
exceptions. java.util.Scanner
implements AutoCloseable
and as such has a close()
method.
When constructed with file as argument, it will use said file as input and close the file, when the scanner is closed. It is good practice to close
a resource once it has been 'consumed', because this releases the resource. The purpose of such a close
operation is to signal that the resource should be brought to a state
that is safe and can be freed for other use.
Classes that have such properties are known by the generic name
resource, something that you need but is potentially scarce.
Since Java 7, we have a feature called try with resources . It adds a new construct to the try-catch block, namely
the resources that you are about to use. There can be more than one resource.
Once the try with resources is done with the resources, the resources
are automatically closed and freed. For this to work, the resource must implement the java.lang.Autoclosable
interface.
You also save yourselves the tedious work of closing when an exception
occurred. That is also taken care of. So when your Java level is
greater or equal to Java 7, use try-with-resources whenever you can, because it will save you the work
of closing the resources in the finally block.
You declare the resources in the try-with-resources in the natural order, the earlier resource being used by those further on. They will be closed in the reverse order of declaration. In the above case that would mean:
- Scanner scanner closed
- InputStream in closed
- File f closed
which is the natural order of freeing or closing the resources.
Choose: at least one of catch or finally block
Note that the compiler insists on having at least one of
- a catch block (multiple allowed) or
- a finally block (at most one)
So try{…}
is not allowed, but try(AutoCloseable resource){…}
is, because the compiler will construct an implicit finally block for you.
It is the same idea of having a default constructor, when you did not write any.
If you decide to not deal with any checked exception, your method can simply pass them on to the caller, and let the caller deal with the situation.
Chaining of exceptions
As can be seen from the class diagram in figure Exception hierarchy with Throwable at the top, all exceptions are descendants of java.lang.Throwable
.
Exceptions can be chained, that is, one exception can hold a reference to another exception that is the actual cause of the exception being thrown. (Since Java 1.4 for all Throwables).
If you look at the API doc of java.lang.Throwable
, you will see that it provides several constructors.
Three of them allow you to add as parameter of type Throwable, with the formal and aptly chosen name cause. The receiver of the Throwable (or should I say catcher) can inspect any descendant of Throwable about a deeper cause, using the method Throwable.getCause()
. This can be seen as chain of Throwables, also called nested exceptions.
Although you cannot simply iterate through these exceptions, getting all in a loop is simple:
Logging
Logging can be a very powerful instrument to squeeze very much or very precise information from a running application. It can be configured without modifying the source code. Use logging instead of system.out.print
Replace soutv
by using a log macro
When you use a supplier to supply the message that is logged, when logging is active, logging can become cheap. To make this work, you might want to teach your IDE to help a bit here, by adding a keyboard macro that adds logging instead of simple System.out.println(…).
Now logging becomes simpler than ever.
- First create a logger with the already define macro
logr
[tab] and then - inside the methods use
logv
[tab] instead of [black]soutv
wherever you would like to output stuff for debugging.
In your logging.properties file add the proper level for your class or as global and you can turn the logging on or off whenever you need to.
Configure logging
When you copy the file logging.properties from its default location $JAVA_HOME/conf/logging.properties
to your projects
directory, preferably under a conf
directory, you can modify and adapt that file to make logging effective in a very precise way,
by specifying what level you want for what class.
In your IDE add -Djava.util.logging.config.file=conf/logging.properties
to the java run actions.
Say the class that needs debugging is called logging.Main
. Then in the logging properties file (see above) add
logging.Main.level = FINE
, and make sure that you start the JVM with the proper login properties file.
Read a bit more about logging in the book and get some additional information using https://sematext.com/blog/java-logging/.