byte b = 4;
String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'))
A class or interface of type T will be initialized before the first occurrence of any of the following:
- instantiation of T
- invoking a static method of T
- T static field assignment
- static field (N.B. not a constant) declared in T is used
- when a class is initialized, so do their super classes and superinterfaces containing default methods
- a reference to a static (n.b. not a constant) field causes initialization of only the class or interface that declares it (ignoring their possible superclasses)
Therefore, the below class does not initialize the Child class (i.e. executing its static initializer block) regardless of referencing a static parent field from it.
class Super {
static String id = "Super"; // This member could also be a constant
}
class Child {
static {
System.out.printf("Static initializer block...%n");
}
}
public class Test {
public static void main(String...args) {
System.out.print(Super.id); // It'll only print "Super"
}
}
public String indent(int numberSpaces)
→ adds/removes the same number of blank spaces of each line. It's worth noting that if absent, it'll add a new line (\n) to the end of the string.public String stripIndent()
→ It removes all incidental whitespace, but does not add a trailing line break if missing like the above method.
Example:
final var greeting = " hola\n"
.concat(" hello\n")
.concat(" bonjour.");
greeting.indent(-1)/**
*hola\n
* hello\n
*bonjour\n
*/
greeting.indent(-2)/**
*hola\n
*hello\n
*bonjour\n
*/
greeting.indent(0) /**
* hola\n
* hello\n
note the \n added → * bonjour\n
*/
greeting.stripIndent()/**
*hola\n
* hello\n
\n not added !!! → *bonjour
*/
Strings in Java are naturally ordered (i.e. ascending from smaller to bigger) in the following order:
- Numbers
CharSequences
in capital lettersCharSequences
in small letters
e.g. Given
String[] array = {"abc", "T", "TOTO", "123", "1", "2", "_1_2", "xyz", "a", "x", "z", "y", "xy", "b", "c", "d", "ab", "ba", "ac", "ca"};
Arrays.sort(array); // It'll become: [1, 123, 2, T, TOTO, _1_2, a, ab, abc, ac, b, ba, c, ca, d, x, xy, xyz, y, z]
ZonedDateTime
contains anOffSetDateTime
as well as a Zone. However, both types can be initialized as:
OffsetDateTime.now(ZoneId.of("America/La_Paz")) // 2022-06-06T14:12:06.294197-04:00
ZonedDateTime.now(ZoneId.of("America/La_Paz")) // 2022-06-06T14:12:06.294197-04:00[America/La_Paz]
One fundamental difference relies on the fact that ZonedDateTime
allows daylight saving calculations:
// Given the US's spring forward:
ZonedDateTime.of(LocalDateTime.parse("2022-03-13T01:30:00"), ZoneId.of("America/Los_Angeles"))
.plusHours(1) // 2022-03-13T03:30-07:00[America/Los_Angeles]
// Whereas
OffsetDateTime.of(LocalDateTime.parse("2022-03-13T01:30:00"), ZoneOffset.of("-08:00"))
.plusHours(1) //2022-03-13T02:30-08:00
// It's worth noting that CEST timezone is not accepted by ZoneId class, instead always use CET:
ZonedDateTime.of(LocalDateTime.parse("2022-03-27T01:30:00"), ZoneId.of("CET"))
.plusHours(1) // 2022-03-27T03:30+02:00[CET]
- The most important patterns used by
SimpleDateFormat
andDateTimeFormatter
are:
Symbol | Meaning | Example |
---|---|---|
y yy yyyy yyyyy [*] |
year, don't confuse with Y which represents week of the year |
2022 22 2022 02022 |
M MM MMM MMMM MMMMM[*] |
month, it does not work with m as it's minute |
6 06 Jun June J |
d or dd | day of the month, it does not work with D as it representsthe day of the year |
15 |
e ee eee eeee eeeee [*] |
day of the week, it also works with E |
3 03 Wed Wednesday W |
h hh |
hour of the day, it also works with H |
5 05 |
m mm |
minute, it does not work with M as it's month |
1 01 |
s ss S SS SSS SSSS...[*] |
S means fraction of second, whereas s is the second of the minute |
1 01 1 01 013 0135 |
a | after meridian or past meridian | AM or PM |
z, zz, zzz zzzz |
Timezone name | BOT or CEST, Bolivian Time or Central European Summer Time |
Z, ZZ, ZZZ ZZZZ ZZZZZ[*] |
Timezone offset | -0400 or +0200 GMT-04:00 or GMT+02:00 -04:00 or +02:00 |
for the exhaustive list, refer to the DateTimeFormatter 's javaDoc |
[*] Having a pattern more than 5 times leads to an exception
DateTimeFormatter
contains 4FormatStyles
: given
DateTimeFormatter
.ofLocalizedDate(formatStyle)
.withLocale(new Locale.Builder().setLanguageTag(locale).build())
.format(Instant.now())
where formatStyle and locale are:
FormatStyle | locale es-BO | locale en-US | locale fr-FR |
---|---|---|---|
SHORT | 6/6/2022 | 2022-06-06 | 06/06/2022 |
MEDIUM | 6 jun. de 2022 | Jun 6, 2022 | 6 juin 2022 |
LONG | 6 de junio de 2022 | June 6, 2022 | lundi 6 juin 2022 |
FULL | lunes, 6 de junio de 2022 | Monday, June 6, 2022 | lundi 6 juin 2022 |
- UTC uses the same time zone zero as GMT, therefore they are equivalent
- Z at the end of some dates means Zulu or zero timezone (not to be confused with 0 offset)
- It's possible to truncate some parts of the time:
// Here we ignore the minutes
LocalTime.of(1, 10)
.truncatedTo(ChronoUnit.HOURS) // 01:00
Temporal adjusters allow to modify temporal objects by externalizing the adjustement process while leveraring the strategy pattern. There are two ways to use temporal adjusters:
temporal = adjuster.adjustInto(temporal)
temporal = temporal.with(adjuster)
- The replace method from
StringBuilder
takes 3 arguments:
StringBuilder replace(int start, int end, String str) {
whereas the one from String
comes in two flavors:
String replace(char oldChar, char newChar)
String replace(CharSequence target, CharSequence replacement)
Given the methods:
void doStuff(int i) {...}
void doStuff(long l) {...}
void doStuff(Integer i) {...}
void doStuff(int...args) {...}
The below table shows the order by which Java resolves a method when overloaded.
Rule |
---|
exact match by type |
larger primitive type |
autoboxing |
vargs |
- Parent class static content (i.e. steps 2 to 3)
- static variable declaration order-wise
- static blocks order-wise
- if there's a superclass, initialize it first (i.e. steps 5 to 7)
- instance variable declaration order-wise
- instance block order-wise
- initialize constructor
example:
class Parent {
static {
System.out.print("A"); // 1
}
{
System.out.print("B"); // 3
}
public Parent(String name) {
this(1);
System.out.print("C"); // 5
}
public Parent() {
System.out.print("D");
}
public Parent(int stripes) {
System.out.print("E"); // 4
}
}
class Child extends Parent {
static {
System.out.print("F"); // 2
}
{
System.out.print("H"); // 6
}
public Child(int stripes) {
super("");
System.out.print("G"); // 7
}
public static void main(String[] grass) {
new Child(1); // AFBECHG
}
}
- The method in child must have the same signature (i.e. name + args) as the parent's one
- The method in child must be at least as accessible as the method in the parent class
- The method in child may not declare any exceptions (including checked, or else it cannot be broader than the checked exception of the parent)
- The child return type must be covariant i.e. the overriding return type should be assignable to the parent return type without a cast
Java does not allow variables to be overriden, they can be hidden though. They are resolved based on the type of the reference and not the instance type conversely to what occurs with polymorphic method calls.
Likewise, static methods can be hidden if the parent's method is not declared as final.
Why do we need sealed classes? It strengthens domain modeling by allowing closed class hierarchy representation. It ultimately enhances the new Java pattern matching feature with switch statement.
Some new keywords:
sealed
→ indicates that a class (concrete or abstract) or interface may only be extended/implemented by named classes or interfaces.permits
→ used along withsealed
, lists the classes/interfaces that can extend/implement it. It's usage depends on the location of the subclasses:- In a different package → required
- In the same file as the sealed class → not required, but permitted.
- Nested class of the sealed class → not required, but permitted.
non-sealed
→ applies to any class/interface extending/implementing the sealed class, indicating that it can be extended/ implemented by unspecified classes.
Direct subclasses of sealed classes must be marked final, sealed or non-sealed (excluding for interfaces for which they can be defined as either sealed or non-sealed).
ℹ️ Sealed classes and their sub-classes must reside in the same module.
Given:
record Person(String fullName, int age) {}
We can leverage compact constructors to process (i.e. validate or alter) constructor arguments before the actual constructor is invoked:
record Person(String fullName, int age) {
Person { // Mind that () aren't required, nor the list of arguments
if (fullName == null || fullName.isBlank()) {
throw new IllegalArgumentException("fullname invalid format.");
}
age++; // Allowed, but discouraged !!!
}
}
- A
record
cannot extend from a class, only implement interfaces. - A
record
's canonical constructor, compact constructor, and getters cannot throw any checked exception (i.e.throws Exception
) - A
record
cannot have a compact constructor with an explicit canonical constructor as the compact constructor is kind of a canonical constructor - The first line of a non-canonical constructor must call another record constructor
- An inner record is implicitly static
- A record is implicitly
final
and cannot be superseded by sealed, non-sealed or abstract
Lambdas aren't allowed to redeclare local variables. They're only allowed to reference final or effectively final variables.
void doStuff() {
var var = "foo";
final var var1 = "foo";
// String p = ""; // If this line would be uncommented out, then the below line wouldn't compile
Predicate<String> p = string -> {
String var1 = ""; // not allowed
print(var); // allowed if and only if var is effectively final
return true;
};
}
Functional interfaces must only have one abstract class excluding any redefining method of the class Object (i.e.
toString
, hashCode
, equals
, etc).
According to Robert Martin, the usage of funtional interfaces can reinforce the SOLID principles adoption. A clear example is the Command design pattern.
It's important to note that there're two ArrayList implementations:
- The one from
java.util.
, and: - The private static inner class from
java.util.Arrays
→ this is an implementationAbstractList
which does not allow elements removal.
You've probably encountered yourself in a situation where you needed to convert a collection into an array:
// Given
final var list = List.of(1, 2, 3);
// Traditional way:
final Object[] array = list.toArray();
// Or, strongly typed:
final int[] array = list.toArray(new Integer[0]);
// Or, a bit better
final int[] array = list.toArray(new Integer[list.size()]);
// However, there is this good-looking option with method reference:
final int[] array = list.toArray(Integer[]::new);
java.util.ArrayDeque
is combination of stack and queue, the exhaustive list of methods can be found here.
add == addLast == offerLast == offer
addFirst == push == offerFirst
poll == pollFirst == pop == getFirst == element == peek == peekFirst == remove == removeFirst
getLast == peekLast == removeLast == pollLast
Mind that there are some methods that remove and return the element such as remove and pop.
It's possible to create records with generics, take a look at the following simplified Monad implementation:
record IdentityFunctor<T>(T value) {
IdentityFunctor {
Objects.requireNonNull(value, "The value cannot be null.");
}
public <R> IdentityFunctor<R> map(final Function<T, R> mapper) {
return new IdentityFunctor<>(mapper.apply(value));
}
public <R> R match(final Function<T, R> mapper) {
return mapper.apply(value);
}
}
Provides a level of control when processing, particularly useful when applying parallel algorithms on large datasources.
Given:
final var original = Stream.of(1, 2, 3, 4, 5).spliterator();
final var firstHalf = original.trySplit(); // firstHalf will hence contain [1, 2], whereas the original spliterator [3, 4, 5]
original.tryAdvance(System.out::print); // 3, then the original spliterator will contain [4, 5]
original.forEachRemaining(System.out::print); // 45
original.tryAdvance(System.out::print); // Does not print anything and returns false
original.trySplit(); // return null as no elements are present anymore.
Turning a stream or collection into a Spliterator:
final Spliterator<?> splitetator = stream.spliterator();
final Spliterator<?> splitetator = collection.spliterator();
And conversely:
final Stream<?> stream = StreamSupport.stream(splitetator, false);
It's worth noting that .spliterator() on a stream is a terminal operation, and it can be applied on infinite streams. In that situation, the method getExactSizeIfKnown() will return -1 and estimatedSize() will return Long#MAX_VALUE.
- Sequential streams run on a single thread (N.B. they aren't necessarily ordered)
- Some operations (intermediary especially) of parallel stream run on multiple threads to improve performance
- Encounter order dictates whether a stream is considered to be ordered or not, this is defined by the source or intermediate
operations (depending on the collection implementation, if it's an array or list, then the stream will be ordered whereas
for a TreeSet, it'll be the contrary). The only way to order streams is by calling the
sort()
method - As a side note to the above statement, they depend on the source implementation, although for any stream it's possible to call
unordered()
so that the stream becomes unordered, thus potential optimizations can be performed by the JVM
Allows to merge two collectors into a single object:
final var totalElementsAndTotal = Stream.of(1, 2, 3, 4, 5)
.collect(Collectors.teeing(Collectors.counting(), // counts the total elements in the stream
Collectors.summingInt(Integer::intValue), // sums up their values
Pair::of)); // we define the object on which collect both results
System.out.println(totalElementsAndTotal); // It'll print (5, 15)
or
final var pairAndOddNumbers = Stream.of(1, 2, 3, 4, 5)
.collect(teeing(filtering(num -> (num & 1) == 0, counting()), // Pair
filtering(num -> (num & 1) != 0, counting()), // Odd
Pair::of));
System.out.println(pairAndOddNumbers); // (2, 3)
To avoid creating custom value objects, there is a useful technique that consists to create an anonymous object with with custom instance fields:
Map<Integer, String> aMap = someMap();
aMap.entrySet()
.map(entry -> new Object() {
final int id = entry.getKey();
final String name = entry.getValue();
})
.forEach(tuple -> {
System.out.println(tuple.id); // Note that the variable tuple recognizes the id defined in the .map(...) operation
});
Given:
UnaryOperator<Integer> f = x -> x++; // This won't ever increment the value as it's using a pre-incrementor
// We should instead use `x -> ++x`
The below symbols work with out#printf
, String#formatter
, String#format
:
Symbol | Supported type | Additional notes |
---|---|---|
%s | string | N/A |
%d | int or long | N/A |
%f | float or double | Let's take the example of [3.14159285] By default it considers 6 decimals → [3.14159] %.3f will round to two decimals → [3.142] %5.2f adds spaces to the left → [ 3.14] %05.2f adds zeros to the left → [03.14] |
%n | \n | It adds a new line independently of the operating system. |
%x | byte | Hexadecimal format. |
The below symbols work with NumberFormat#format
:
Symbol | Description | example |
---|---|---|
# | Omits position if digit is absent | new DecimalFormat("##.00").format(3.141592) → [3.14] |
0 | Fills in with 0's if digit is absent | new DecimalFormat("00.00").format(3.141592) → [03.14] |
There is an old but useful class to replace placeholders in the format {0}, {1}, ...
System.out.println(MessageFormat.format("{0} {1}!!! {2}", "Hello", "world", 2022)); // Hello world!!! 2,022
Locale#toString
formats the locales aslanguage abbreviation in small letters
+_
+country abbreviation in capital letters
, e.g.es_ES,
en_US,
fr_FR,
es_BO
- IETF languages tags are in the form
es-ES
,en-US
,fr-FR
andes-BO
⚠️ mind the-
instead of_
. For Java to correctly load this kind of format, we have two options:var locale = Locale.forLanguageTag("en-US")
, →locale.toString(); // en_US
var locale = new Locale("en", "US");
→locale.toString(); // en_US
⚠️ deprecated in Java 19, instead favorLocale.of("en", "US");
- To check whether a locale is valid:
asList(Locale.getAvailableLocales()).contains(locale)
- It's possible to set finer-grained control of the default locale by setting the
Locale#Category
(eitherDISPLAY
orFORMAT
) by callingLocale.setDefault(category, locale)
- A locale consists of a required lowercase language code and optional uppercase country code.
final var amount = 50.99;
System.out.println(NumberFormat.getCurrencyInstance(Locale.forLanguageTag("en-US"))
.format(amount)); // $50.99
System.out.println(NumberFormat.getCurrencyInstance(Locale.forLanguageTag("en-GB"))
.format(amount)); // £50.99
System.out.println(NumberFormat.getCurrencyInstance(Locale.forLanguageTag("fr-FR"))
.format(amount)); // 50.99 €
System.out.println(NumberFormat.getCurrencyInstance(Locale.forLanguageTag("es-BO"))
.format(amount)); // Bs50.99
It's designed to be used in places where print space may be limited. There are two self-explanatory styles available:
- LONG
- SHORT
CompactNumberFormat.getInstance(Locale.US, SHORT).format(1_400_000_000); // 1B
CompactNumberFormat.getInstance(Locale.US, LONG).format(1_400_000_000); // 1 billion
CompactNumberFormat.getInstance(Locale.FR, SHORT).format(1_500_000_000); // 2 Md, ⚠️ mind that rounding is applied
CompactNumberFormat.getInstance(Locale.FR, LONG).format(1_500_000_000); // 2 milliards
CompactNumberFormat.getInstance(Locale.forLanguageTag("es-BO"), SHORT).format(1_400_000_000); // 1400 M
CompactNumberFormat.getInstance(Locale.forLanguageTag("es-BO"), LONG).format(1_400_000_000); // 1400 millones
It certainly becomes an alternative to Instant as it provides an abstraction to Clock, meaning that we should no longer
need to mock
the Clock for unit testing. Besides, it allows to plug diverse InstantSources when needed:
import java.time.InstantSource;
...
@RequiredArgsConstructor
class Bean {
@Autowire
private final InstantSource instantSource;
public boolean isBefore(final Instant toCompare) {
return instantSource.instant().isBefore(toCompare);
}
}
@Configuration
class Config {
@Bean
public InstantSource instantSource() {
return Clock.systemUTC();
}
}
class UnitTest {
@Test
should_test_something {
//...
// When
new Bean(InstantSource.fixed(Instant.EPOCH)).isBefore(customDate);
//...
}
}
// If not found, a MissingResourceException is thrown.
ResourceBundle.getBundle("filename", Locale.US); // Will look in the module/class path for the file called filename_en_US.properties, if not found filename_en.properties, filename_US.properties, filename.properties and so on.
Properties properties = new Properties();
System.out.println(properties.get("key")); // null
System.out.println(properties.getProperty("key")); // null
System.out.println(properties.getProperty("key", "defaultValue")); // defaultValue
For a more detailed description, refer to the OCP Java 11 study notes | modules section
Compiling a class within a module:
javac -p mods -d directory classesToCompile
Running a main method from a class from a module:
java -p directory -m moduleName/pathToClass.ClassName
Generating a jar out of the compiled files:
jar -cfv mods/dep.jar -C directory/ .
Running the same class from the jar file:
java -p mods -m moduleName/pathToClass.ClassName
The easiest approach, and recommended when you have the control of all the application dependencies. Converting lower-level libraries into modular jars before converting higher-level libraries.
Example:
A.jar
depends on B.jar
, which likewise depends on C.jar
- Step 1: Pick the lowest-level dependency, in this case, modularize
C.jar
by making it a named module (at this point, bothB.jar
andA.jar
will remain unnamed modules and therefore will be able to accessC.jar
). - Step 2: Repeat the process for
B.jar
and later on forA.jar
The ultimate goal after the migration is to have all packages residing on the module path (and thus none on the classpath).
Recommended approach when we don't have control of every jar used by our applications (i.e. become automatic modules)
The idea is to put all dependencies on the module path and make the highest-level dependency a named module, adding as
many requires
as needed in accordance to the default automatic modules' names.
Example:
A.jar
depends on B.jar
, which likewise depends on C.jar
, thus,
- Step 1: Move
A.jar
,B.jar
, andC.jar
to the module path (then they all become automatic modules). - Step 2: Make
A.jar
a named module by adding the necessary required in themodule-info.java
file
- happens-before relationship is a guarantee that any action performed in any thread is reflected to other
actions in different threads.
Stream#forEachOrdered
is an example of executions that comply with this term. - The
volatile
keyword ensures that only one thread is modifying a variable at a time and that data read by multiple threads is consistent. However, it does not provide thread-safety. As an example, the usage of the volatile keyword on a variable x ensures that if one thread updates x, then whichever thread is executed after will be aware of the latest updates on x atomic
is the property of an operation to be run as a single unit of execution without any interference from another threadMutual exclusion
is the property that at most one thread is executing a segment of code at a given time. This can be implemented with a lock or monitorfairness
happens when two threads waiting to acquire a lock, the first to enter the block of code will be the first that entered the waiting list. It's is disabled by default withReentrantLock
and unavailable withsynchronized
blocks- Intrinsic locking = synchronized + volatile
- Explicit locking = manual lock
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
Map<String, Object> cache = new HashMap();
// Mind that Collections.synchronizedMap(new HashMap()) could also work but would be inefficient.
/**
* Note that any number of threads can call this method at the same time.
*/
public Object readFromCache(final String key) {
try {
readLock.lock();
return cache.get(key);
} finally {
readLock.unlock();
}
}
/**
* However, if a write operation is performed, the write lock will protect the modification of the cache and prevent
* concurrent reads that could read corrupted or inconsistent values.
*/
public void writeToCache(final String key, final Object value) {
try {
writeLock.lock();
cache.putIfAbsent(key, value);
} finally {
writeLock.unlock();
}
}
// Note that a ConcurrentHashMap accomplishes the same goal
Are a kind of a lock and the different lies on the fact that a lock allows only one thread to access a block of code, whereas a semaphore can rely on a number of permits to allow multiple threads to concurrently access a guarded block of code. In other words, a lock has only 1 permit.
Semaphore semaphore = new Semaphore(5);
try {
semaphore.acquire();
// guarded block of code: this block is allowed to be executed by 5 threads at the same time
} finally {
semaphore.release();
}
- A barrier allows several tasks (run in different threads) wait for each other, then trigger a subsequent task or action and then reset the system, so that it can run again.
- A latch works almost in the same way as barriers, the only different is that it does not reset once a latch is open it cannot be closed again.
import java.util.concurrent.CyclicBarrier;
class Foo implements Callable<Boolean> {
private final CyclicBarrier barrier;
public Boolean call() throws Exception {
try {
System.out.print("Waiting...");
barrier.await();
// do something
System.out.print("Done");
return true;
} catch (InterruptedException e) {
System.out.print("Interrupted");
}
return false;
}
public static void main(final String...args) {
final var cyclicBarrier = new CyclicBarrier(4, () -> System.out.println("barrier opened"));
final var executorService = Executors.newFixedThreadPool(4);
List<Future<Boolean>> futures = new ArrayList<>();
try {
for (int i = 0; i < 4; i++) {
Foo foo = new Foo(cyclicBarrier);
futures.add(executorService.submit(foo));
}
futures.forEach(it -> {
try {
it.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
} finally {
executorService.shutdown();
}
}
// It'll print:
// Waiting...
// Waiting...
// Waiting...
// Waiting...
// barrier opened
// done
// done
// done
// done
}
When reading from a Reader
implementation, -1 or null implies end of file. In hindsight, when reading
from an OutputStream
implementation, an EOFException
is thrown instead.
- It extends Object (and not any nio Stream), it implements
Autocloseable
though - Allows to read, write, and delete a random access file. The valid modes are
r
,rw
,rws
, andrwd
(new RandomAccessFile(file, mode)
) - Behind the scenes, it works with a cursor whose position can be manipulated with the method
seek(position)
- When reaching the end of a file, it throws a
EOFException
- The method to write a String is
raf.writeChars(string)
and the method to read a lineraf.readLine()
(beware that both methods throwIOException
)
final var ps = DriverManager.getConnection("URL").preparedStatement("sql")
if (ps.execute()) { // the query was of type select and hence we can retrieve a ResultSet
final var rs = ps.getResultSet();
//...
} else {
final var updatedLines = ps.getUpdateCount();
// ...
}
Note that if we attempt to retrieve the ResultSet from a non select query (i.e. ps.executeQuery()
), we get a SQLException
,
same goes to calling ps.executeUpdate()
on a select query.
ℹ In JDBC we start counting from 1 and not 0.
Before reading from the ResultSet
, we must ensure that firstly ps.next()
is called to allow the cursor to traverse
the results, and secondly, return true
so that we are sure we got results:
final var sql = "SELECT count(*) AS count FROM users";
try (final var ps = conn.prepareStatement(sql);
final var rs = ps.executeQuery()) {
if (rs.next()) {
System.out.println(rs.getInt("count"));
}
}
ℹ️ If for whatever reason we need to pass a literal to a query "select * from user where type = '" + type + "'";
, in order
to avoid SQL injection, it's recommended to leverage Statement's enquoteLiteral
method:
var query = "select * from user where type = " + stmt.enquoteLiteral(type) + ";";
Now, if we want to apply exact match (e.g. table user and not USER nor User), we can also leverage Statement's enquoteIdentifier
method:
var query = "select * from " + stmt.enquoteIdentifier("user") + " where type = " + stmt.enquoteLiteral(type) + ";";
By default, transactions are set to autocommit, meaning that once the Statement implementation is run, changes (if applied) are instantly performed in DB.
There is though a way to disable this behavior, by calling connection.setAutoCommit(false)
.
try(final var connection = DriverManager.getConnection("url", "user", "password");
final var ps = connection.prepareStatement("TRUNCATE TABLE USERS")) {
connection.setAutoCommit(false);
try(final var rs = ps.executeUpdate()) {
System.out.println(rs > 0 ? "succcess" : "failure");
} catch(SQLException exception) {
System.err.printf("Something went wrong %s%n", exception);
connection.rollback();
}
}
There are two type of exceptions:
- checked: All exceptions extending from Exception or Throwable → we are forced to handle them.
- unchecked: All exceptions extending from RuntimeException or Error → we aren't forced to handle them.
Checked exceptions are debatable considered a Java design flaw, and the recommended
approach is to create a runtime counterpart
(e.g. For IOException
, there is a UncheckedIOException
introduced in Java 8). There are nevertheless other elegant
techniques to handle exceptions such as Vavr's Try, API.unchecked, Jool's unchecked or Apache Commons' asRuntimeException.
Depending on the application requirements and error handling rules, there is a more-functional-friendly alternative
without side-effects that consists in declaring a contract of return type in the formEither<Error, Success>
, but
that's way out of the scope of the present document and has pros and cons.
Important
Be aware that using those jool, vavr or Apache Commons fancy tools to turn checked into unchecked exceptions behind a Cglib proxy as you might end up facing the obscure UndeclaredThrowableException, I coded an example here.
Pattern matching is a technique for controlling program flow that only executes a section of code that meets certain criteria. Moreover, it's used in conjunction with if statements for greater program control.
// Without pattern matching
final Number number = someNumber();
if (number instanceOf Integer) {
((Integer) number)++;
}
// With pattern matching
final Number number = someNumber();
if (number instanceOf Integer anInteger) {
anInteger++;
}
// But if we were applying FP by the book...
final Number number = someNumber();
if (number instanceOf final Integer anInteger) {
final var increasedInteger = increase(anInteger, 1);
}
// We can also add some pre-conditions:
final Number number = someNumber();
if (number instanceOf final Integer anInteger && anInteger != null) {
final var increasedInteger = increase(anInteger, 1);
}
The switch statement can from now on return values:
// Without fallthrough:
// Statement
int numLetters;
switch(seasonName) {
case "Fall" -> numLetters = 4;
case "Spring" -> {
System.out.println("Spring time !!!");
numLetters = 6;
}
case "Summer", "Winter" -> numLetters = 6;
default -> numLetters = -1;
}
// Expression
int numLetters = switch(seasonName) {
case "Fall" -> 4;
case "Spring" -> {
System.out.println("Spring time !!!");
yield 6;
}
case "Summer", "Winter" -> 6;
default -> -1;
};
// With fallthrough:
// Statement
int numLetters;
switch(seasonName) {
case "Fall":
numLetters = 4;
break;
case "Spring":
System.out.println("Spring time !!!");
case "Summer", "Winter":
numLetters = 6;
break;
default:
numLetters = -1;
}
// Expression
int numLetters = switch(seasonName) {
case "Fall":
yield 4;
case "Spring":
System.out.println("Spring time !!!");
case "Summer", "Winter":
yield 6;
default:
yield -1;
}
These interfaces contain a bunch of navigation methods reporting closest matches for given search targets.
final NavigableSet<String> set = new TreeSet<>();
set.addAll(List.of("a", "cc", "b", "c", "bb", "aa")); // a aa b bb c cc
System.out.println(set.ceiling("bbb")); // returns c, greater than or equal to
System.out.println(set.lower("aa")); // returns a, the lower element (null if not found)
System.out.println(set.floor("bbb")); // returns bb, less than or equal
System.out.println(set.higher("cc")); // returns null no higher element found
System.out.println(set.headSet("aa", true)); // returns [a aa]
System.out.println(set.subSet("b", "cc")); // returns [b bb c], note that the to is not inclusive !!!
System.out.println(set.subSet("b", true, "cc", true)); // returns [b bb c cc] same as above but with inclusive final element
System.out.println(set.tailSet("c")); // returns [c cc]
System.out.println(set.pollFirst()); // returns a from the set and removes it from the set
System.out.println(set.pollLast()); // returns cc from the set and removes it from the set
Methods returning a collection throw an IllegalArgumentException
if keys are out of range (e.g. adding c
to the subset of aa
).
Note that the same methods are available for NavigbleMap
interface, and therefore its implementations.
Oftentimes, we're faced with situations when we don't necessarily require to implement all methods exposed by an interface (c.f. interface segregation principle from SOLID principles). There is a technique rarely used to tackle this requirement, it's about using an encapsulated version of the original interface. Let's consider the below example:
interface Repository<T, ID> { // Mind the access level
T save(T entity);
Optional<T> get(ID id);
List<T> findAll();
void deleteAll(); // ⚠ dangerous method
}
We can create another interface (mind that this one will be exposed) containing only the methods we want to expose:
public interface EncapsulatedRepository<T, ID> { // Mind the access level
T save(T entity);
Optional<T> get(ID id);
List<T> findAll();
// 😄 no dangerous method exposed
}
So that the original interface can extend the encapsulated one:
interface Repository<T, ID> extends EncapsulatedRepository<T, ID>{
...
}
As a matter of fact, the client will use the EncapsulatedRepository
which has no dangerous method whatsoever.
I created a working example for a Spring Data Jdbc's interface, available here.
I found an alternative technique that consists in using an inner interface implementation and composition, article available here.
Introduced in Java 7, they'are meant automatically close any object implementing Autocloseable
(including Closeable
from
Java 5):
record AutocloseableObject() implements AutoCloseable {
void close() throws Exception {
// closing logic...
}
}
var final autocloseable = new AutocloseableObject();
try (autocleable){
// or try (var autocloseable = new AutocloseableObject()) { // -> mind that final is implicit
} catch(Exception e) {
}
Mind that variables used by the try-with-resources block must be effectively final.
Although static members are actually hidden, there's a special case wherein classes can use a static method "as if they were inherited" without requiring its declaring class:
abstract class Parent {
public static final String ID = "id";
public static void foo() {...}
}
interface Inter {
String ID = "id";
public static void foo() {...}
}
class Child extends Parent implements Inter {
public static void main(String[] args) {
foo(); // This will call the method from the abstract class.
doSomething(ID); // This is also valid and will reference the ID from the Parent class
// To call the static method or reference the ID from the interface, we need to explicitly preceed the member by Inter
}
// Mind that overriding the method would not compile
}
strip
is the "Unicode-aware" version of trim
.
trim
removes only characters <= U+0020
(space); whereas strip
removes all Unicode whitespace characters (but not
all control characters, such as \0
)
Now we can provide a Function<? extends String, R>
to map a String into something else 🧐 curious to find out why
they decided to call it transform and not map as done in the Stream API), regardless below an example:
String aString = "foo";
Function<String, Integer> function = String::length; // mind that we can functions as parameters which is powerful
int length = aString.transform(function); //
Introduced in Java 5, it allows to perform a custom formatting of %s
when an object (implementing Formatable
) is
passed as argument to a Formatter#format
:
class Bear extends Animal implements Formattable {
@Override
public void formatTo(final Formatter formatter, final int flags, final int width, final int precision) {
formatter.format("Bear");
}
}
class Animal {
public String toString() {
return "%s".formatted(this); // → %s will be replaced by Bear
}
}
class Main {
public static void main(String[] args) {
System.out.println(new Bear().toString()); // Will pring Bear
}
}