This is a collection, in no particular order, of best practices learned and shared over a period of years. Some are specific to Core Java; others are good programming practice independent of language.
Do you have corrections or suggestions? Send me an email.
These are strongly suggested, to make sure that when multiple engineers edit the same code, the only changes are to the code, not wholesale changes to files. This makes code reviews consistent.
Now, some specifics:
if (foo == 1); // ← note semicolon
callDangerousMethod(); // called even when foo != 1
access-modifier [abstract] class ClassName
[extends … implements …] {
constants
fields
constructors
abstract methods
public/protected methods
private methods
accessors
}
public class Foo {
public long field1;
long field2; // package-private
private long field3;
}
We all remember PEMDAS from elementary school, right? Parentheses, Exponents, Multiplication/Division, Addition/Subtraction. (I’ve seen some remember a different mnemonic, BEDMAS, where parentheses are called “braces,” and equal-precedence operations got switched, but that’s the same thing. Sounds like British Commonwealth; “countries separated by a common language” and all that.) Well, Java (like its cousins derived from parent C and grandparents B and BCPL) has its own operator precedence. It’s defined here. It’s important that you know this, or at the very least, know where to look it up.
Also, the IDE can help you. Consider this Boolean expression:
a || b && c
In IntelliJ Idea, if you type Alt-Enter while your cursor is on that expression, one of the options you will see is “Add clarifying parentheses”. Selecting that command will yield:
a || (b && c)
When I tried a real-world expression:
foo.equals(code) || container != null && container.contains(code)
the clarified code became:
foo.equals(code) || ((container != null) && container.contains(code))
This is because != is an operator with higher precedence than &&, which has higher precedence than ||.
To see where operator precedence can lay a pitfall, consider a common pattern in C, with bitmasks defined.
#define BIT_0 1 /* (1 << 0) -- don't do a useless operation */
#define BIT_1 1 << 1
#define BIT_2 1 << 2
#define BIT_3 1 << 3
/* check that bit 2 is set in foo */
if (foo & BIT_2) ...
This works fine, because the & (bitwise and) operator has a lower precedence than the << (shift left) operator. But consider a slightly different operation:
/* check that foo has only bit 3 set (i.e., foo == 8) */
if (foo == BIT_3) ...
Remember that #define just substitutes the text after the symbol for each occurrence of the symbol; there is no evaluation. The equality operator has a higher precedence than the shift operator, so the expression tested in the if is
(foo == 1) << 3
When foo has the value 8, that expression’s value is 0, or false. If foo happened to be 1, the expression evaluates to 8, which evaluates to true. So in those two cases, the expression gives the wrong result. However, if foo’s value is anything else, the expression is false (which is correct). Therefore, we get the right answer, most of the time! Not exactly what we wanted… At least the “clarifying parentheses” would hint that something’s wrong. And, yes, I know that we could fix the defines:
#define BIT_0 1 /* (1 << 0) */
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define BIT_3 (1 << 3 )
Or, do something completely better, such as using actual constants instead of text substitution:
const int BIT_0 = 1;
const int BIT_1 = 1 << 1;
const int BIT_2 = 1 << 2;
const int BIT_3 = 1 << 3;
Optional<Foo> optionalFoo = maybeGetFoo();
optionalFoo.ifPresent(foo -> operateOnFoo(foo));
if (“constant”.equals(variable)) { /* do something */ }
private static final String CONSTANT = “constant”;
public boolean method() {
if (CONSTANT.equals(variable)) { return something; }
}
class Foo {
Object bar;
}
Foo foo = new Foo();
System.err.println(foo.bar.toString()); //crash--bar is null
class Foo {
Field bar;
}
Foo foo = new Foo();
System.err.println(foo.bar); // outputs "null" to stderr
Maven:
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>20.1.0</version>
</dependency>
Gradle:
dependencies { implementation'org.jetbrains:annotations:20.0.1' }
It could be worse. The developers of JavaScript looked at null references, and said, “Hey, that’s cool! Let’s have two of those!”
Most classes in the Java library make immutable objects: they cannot be changed after they are instantiated. String, the temporal classes in java.time, the auto boxed subclasses of Number, are all examples of immutable classes. (Collection classes, such as ArrayList, TreeSet, etc. are mutable, in that elements may be added or removed from a collection.)
The keyword final is used to make a local variable or a member immutable. However, if a final object is mutable, only the reference is immutable; the referent object can still change.
Starting in Java 8, objects are final by default. If a reference takes an assignment after its initialization, it is, by definition not final. However, any reference that is not changed after its initialization is “effectively final” and may be treated as final by language structures.
A class may be declared final. This prevents the class from being extended. An enumeration (keyword enum) is a class that is always final, so the final keyword would be redundant (and an error). An interface may not be final; because interfaces are meant to be implemented elsewhere, they are, by definition, not final.
A method may be declared final. This prevents the method from being overridden a class that extends the base class. Another look at enumerations shows an example; see the section on Enumerations below.
This applies to any programming language. We often want to avoid using “magic numbers,” so we define constants:
public static final int THE_ANSWER = 42;
OK, that’s brilliant. But how did we get 42?
public static final String THE_QUESTION = “What do you get if you multiply six by nine”;
(Source: The Restaurant at the End of the Universe by Douglas Adams, the second of 5 books in the Hitchhiker’s “trilogy.”)
Enough whimsy. There will be times when you want to define a constant that means “one megabyte” (2 to the 20th power). You may simply write:
public static final long ONE_MEGABYTE = 1048576;
which is correct. But the reader would appreciate knowing that you were correct. Instead, write one of the following:
public static final long ONE_READABLE_MEG = 1024 * 1024;
public static final long TWO_TO_THE_TWENTIETH_POWER = 1 << 20;
Which of the following would you prefer to read in someone’s code?
public static final long MILLIS_PER_DAY_TRUST_ME = 86400000L;
public static final long MILLIS_PER_DAY_READABLE = 1000 * 60 * 60 * 24;
Or better still:
public static final long SECONDS_PER_HOUR = 60 * 60;
public static final long SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
public static final long MILLIS_PER_DAY = 1000 * SECONDS_PER_DAY;
The value of MILLIS_PER_DAY_TRUST_ME is correct—trust me! But I did double-check it, just to be sure I had not fat-fingered the number. You can check it yourself, or you can make your constants readable, which makes them easy to check at a glance. You really don’t want to have to find an incorrect constant in the middle of your code. (Trust me!)
If we work in tridecimal (base 13), 6 × 9 actually is 42.
Containers can only contain objects. That’s why Odin (or insert the god of your choice) created autoboxing. But an autoboxed value is an object, that has to be created, and perhaps destroyed. This always involves overhead.
public class Main {
public static void main(String[] args) {
Long twoK = 2000L;
Long dosK = 2000L;
System.err.println("==: " + (twoK == dosK));
System.err.println(".equals(): " + twoK.equals(dosK));
}
}
This program produces the following output:
==: false
.equals(): true
A String object is a sequence of UTF-16 characters or surrogate pairs. (For a description of UTF-16, see this article.) In fact, String implements the interface CharSequence. It is fine to think of a String as a sequence of code points; any code points that do not fit into the Basic Multilingual Plane (BMP) are instead represented by surrogate pairs.
Unnecessary Object Creation – Empty Collections
The class java.util.Collections contains generic methods to return an empty Set, List, or Map cast to any member type; the method names can be considered “intuitive:” Collections.emptySet(), .emptyList(), .emptyMap(). As long as the empty collection you “create” can be considered immutable, you may use these methods to satisfy your needs. The major advantage is, you have a collection reference that is not null, so it can be used in a loop or lambda (that will execute zero times).
Analogous to the empty collections, Collections provides singleton collections: immutable collections that contain exactly one object. Collections.singleton(item) returns a set with one item; Collections.singletonList(item) returns a list; and there is a singletonMap(key, value). The actual implementations are very optimized for speed; and are private to the Collections class. These collections are immutable.
Avoid using .containsKey() when you actually need the value. Calling .containsKey() is nearly as expensive as .get(); if you’re going to call .get() anyway, just call it once (assign the result to a local variable) and compare it with null. (Exception: if the value for an existing key can actually be null, and you care about the distinction from a key that doesn’t exist, you need containsKey() to distinguish this case from when the entry does not exist.)
Most of the collections you will create, such as ArrayList, HashSet, LinkedHashMap, BlockingQueue, are mutable; you may add, insert, and remove elements. Never assume that collections you do not create are mutable. If you need to modify a collection that you did not create, it’s best to copy its elements to a new, mutable collection. In the Javadoc for the interfaces that define collections, methods such as .add(), .put(), .remove() are shown as optional operations, and are either not implemented, or throw an Error, for immutable collections. (Well, calling a method that is not implemented will also throw an Error.)
An enum is (sort of) a class. Classes (including enums) can have fields and methods. But it’s only sort of a class. Mainly, it inherits from Enum (instead of directly from Object); and in that you can’t derive a class from an enum. Inheritance flows from the enum to the members of the enum; that is, you can define a method for the enum, and override it for a single (or multiple) enumeration(s). This can make for client code that is much cleaner than a huge switch statement.
public final boolean equals(Object other) {
return this == other;
}