-
Do not use tabs. When indenting, use four spaces per level of indentation.
-
Use indentation appropriately and consistently, as shown throughout the examples.
-
Lines should not be longer than 120 characters (ideally no longer than 79).
- Ways to produce shorter lines:
-
Chained methods almost always look better when each method call is on its own line, in line with other methods in the chain:
final List<Integer> validIds = inputStrings.stream() .filter(Objects::nonNull) .map(s -> { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException nfe) { return Optional.empty(); } }) .flatMap(Optional::stream) .sorted() .collect(Collectors.toCollection(ArrayList::new));
-
Give arguments their own lines:
final var toSend = MaplePacketCreator.damagePlayer( isSkill() ? skillId : -1, attacker.getId(), player.getId(), damageInstances.get(0), isFake() ? fake : 0, direction, false, 0, true, attacker.getObjectId(), attacker.getPosition().x, attacker.getPosition().y );
-
Put newlines after infix operators and indent appropriately:
final var longString = "This is a really long string. This is a really " + "long string. This is a really long string. This" + " is a really long string. This is a really long" + " string. This is a really long string.";
-
Split out large expressions into multiple local variables and compose them later.
-
- Ways to produce shorter lines:
-
Comment your code.
-
Write javadoc comments directly above all new methods that you create unless the method is particularly obvious (e.g. a getter/setter):
-
Javadoc comments are of this form (NOTE the
/**
as opposed to/*
):/** * `Description of what the method does` * `some more description...` * `etc...` * * <ul> * <li>pure?: `true/false`</li> * <li>nullable?: `true/false`</li> * </ul> * * @param `parameter name` `parameter description` * @param `parameter name` `parameter description` * @param... * * @return `description of return value and what values it can take` * * @throws `exception name` when `condition` * @throws `exception name` when `condition` * @throws... */
-
Of course, the
@param
parts of the javadoc can be omitted if the method takes no parameters,@return
can be omitted if the method returnsvoid
,@throws
can be omitted if the method has no notable exceptions it's prone to throw, andnullable?
can be omitted if the return type isvoid
or the return type is a type that isn'tnull
able (e.g.int
,boolean
, and evenOptional<T>
in this case). -
pure?
refers to whether or not the method is a pure function.
-
-
Block comments should be of the form:
/* * This is a nice block comment. * It's easy to read. * It's pretty. */
-
Single line comments (
//
) should either be on their own line, or be separated from the code that they trail by one or two spaces. In addition, there should be exactly one space between the//
and the contents of the comment, unless the comment is simply disabling ("commenting out") code, in which case there should be no space between//
and the disabled code. -
Use plain english in comments (interspersed with code terms as necessary), with capitalization and punctuation.
-
All source files must end with a trailing newline.
-
All field declarations should be at the top of a class definition.
-
There should be a blank line between field declarations and the first method declaration.
-
There should be blank lines between all method declarations.
-
Class/type names should have camel case for all words, including initialisms:
- Bad:
MAPLE_PARTY_CHARACTER
maplePartyCharacter
maple_party_character
NPCScriptValidator
- Good:
MaplePartyCharacter
NpcScriptValidator
- Bad:
-
Local variables, method parameters, fields, and methods should all have camel case names with the first letter lowercase, including initialisms:
-
Bad:
private void toggle_obstacles_with_id(boolean ReactorId) { int temporaryvariable; // ... } ```
-
Good:
private void toggleObstaclesWithId(boolean reactorId) { int temporaryVariable; // ... }
-
-
Enum values and constants should be in shouting snake case:
-
Bad:
public static final double maceProp = 0.012d;
-
Good:
public static final double MACE_PROP = 0.012d;
-
-
When
catch
ing an exception, the name of the caught exception should be an all-lowercase initialism of the type of the exception (e.g. npe forNullPointerException
or sqle forSQLException
), unless you have a more informative name in mind. -
When
catch
ing an exception and ignoring it (which should only be done in very specific cases and should almost never be done withThrowable
orException
; catch specific exceptions instead), name the caught exceptionignored
and put the closing brace and the immediate next line:-
Bad:
try { theString = Integer.parseInt(n); } catch (Exception e) { }
-
Good:
try { theString = Integer.parseInt(n); } catch (NumberFormatException ignored) { }
-
-
When creating a new class for a specific purpose and that purpose is only really used extensively by one class in particular (i.e. it can be encapsulated by that class), prefer to create a nested class (class definition inside of class definition) over putting the class in its own file.
-
Never start the contents of a pair of curly braces (
{}
) with a blank line, including method and class definitions:-
Bad:
class Foo implements Bar { private int baz; public Foo(int baz) { this.baz = baz; } }
-
Good:
class Foo implements Bar { private int baz; public Foo(int baz) { this.baz = baz; } }
-
-
All floating-point literals should have their types annotated with a lowercase letter:
float myFloat = 0.35f; double myDouble = 11.49101d;
-
long
literals should have their types annotated with an uppercase L:long myLong = 51398010673L;
-
All floating-point literals should have at least one decimal digit before the decimal point, even when that digit is
0
. In addition, there should not be trailing zeroes:-
Bad:
double myDouble = .32147890d;
-
Good:
double myDouble = 0.3214789d;
-
-
Use diamond inference whenever possible:
-
Bad:
List<String> someStrings = new ArrayList<String>();
-
Good:
List<String> someStrings = new ArrayList<>();
-
Also good:
var someStrings = new ArrayList<String>();
-
-
Whenever there exist syntactic tokens separated by commas (e.g. argument lists, array literals), put a single space between each comma and the following item.
-
Array literals (and other possibly similar syntax) should be arranged like the following when it matters for line length/readability:
final int[] someInts = { 415, 5, 837, 461, 101, 694, 147, 3, 81, 919, 555, 319, 79, 597, 66, 461, 267, 567, 6, 191, 352, 353, 444, 537, 859, 0, 97, 444, 231, 296, 1, 987, 202, 51 };
-
Whenever possible, use lambda (
->
) syntax. Do not explicitly create a new instance ofRunnable
and@Override
itsrun()
method. -
Lambda expressions should be styled with the following rules:
-
Use method references instead of using explicit arrow syntax whenever possible:
-
Bad:
final var myInts = strings.stream() .filter(s -> s != null && s.length() > 0) .map(i -> Integer.parseInt(i)) .collect(Collectors.toSet());
-
Good:
final var myInts = strings.stream() .filter(s -> s != null && s.length() > 0) .map(Integer::parseInt) .collect(Collectors.toSet());
-
-
Do not put parentheses around the arguments list of a lambda if there is only one argument.
-
Do not put curly braces around the body of a lambda if the function is a single statement.
-
When putting a line break after the arguments list of a lambda, do so immediately after the
->
if there is only one statement, and immediately after the{
otherwise.
-
-
Do not use the
this
keyword to refer to fields and methods unless necessary, e.g. the name of the field/method has been "shadowed" by a local variable or when using::
notation. -
Use the
sort
method of theCollection
object that is being sorted when doing an in-place sort, as opposed to using the staticCollections.sort()
method. -
if
,else if
,else
,while
,for
,synchronized
,using
,try
, andcatch
blocks should follow the following style:- They must be enclosed with curly braces (
{}
), with one exception:- When there is a stand-alone
if
statement whose condition and body statements are quite short, it can be written all on one line without line breaks.
- When there is a stand-alone
- There should be a single space after the keyword, a single space after the parentheses, a newline after the opening curly brace, and a single newline between the end of the last statement and the closing curly brace:
-
Bad:
if(condition) { foo = new Foo(); } else { foo = null; }
-
Good:
if (condition) { foo = new Foo(); } else { foo = null; }
-
- They must be enclosed with curly braces (
-
Method declarations should be of the following form:
/** * Javadoc * goes * here */ private List<Foo> generateFoos(int fooCount) { // ... return someFoos; }
-
Always use the
@Override
annotation at the top of a method when it overrides another method.
-
Avoid using explicit loops (
for
,while
) and temporary variables (e.g.int i = 0;
,List<Object> objectsCollectedInLoop = new ArrayList<>();
). Prefer to use Java 8 features like.forEach()
and theStream
interface (.map()
,.filter()
,.reduce()
, etc.) instead, unless explicit loops are necessary orStream
methods are problematic in the specific case in question. -
When one has found the need to use explicit loops, use
:
syntax whenever possible:for (final var chr : characterList) { // Do shit }
-
When using the
Stream
inferface, avoid inadvertently dereferencing anull
reference when appropriate like so:// ... .filter(Objects::nonNull) // ...
-
In more ideal situations, when "nullability" is instead represented by
Optional
s being inhabited or empty, filter out emptyOptional
s while simultaneously unwrapping inhabitedOptional
s like so:// ... .flatMap(Optional::stream) // ...
-
If you want to keep the
Optional
s intact in your stream but just filter out empty ones, use this instead:// ... .filter(Optional::isPresent) // ...
-
If you are forced to deal with a stream or collection of nullable objects, you can nicely convert this to the use of
Optional
s like so:// ... .map(Optional::ofNullable) // ...
-
Do not use any other implementation of the
List
interface (e.g.LinkedList
) other thanArrayList
, unless you know exactly what you are doing. -
Prefer to use implementations of the
Map
interface (e.g.HashMap
,LinkedHashMap
) over types of the formList<Pair<T, U>>
, unless you need to be able to index the mappings directly, need it for compatibility with other already existing methods, or you know exactly why you are using aList
instead. -
Prefer to use implementations of the
Set
interface (e.g.TreeSet
,HashSet
) over implementations ofList
when order doesn't matter and there will not be any duplicates (or duplicates can/should be eliminated). Again, if you know that the benefits ofSet
implementations are not needed, then aArrayList
works great (even better). -
Eliminate (or at the very least, comment out) unused
import
statements. -
When declaring local variables, choose to use
var
as the type whenever possible, unless it seriously hurts readability:-
Bad:
for (final MapleCharacter chr : characterList) { // Do shit } final MapleMapObject mmo = currMap.getMapObjs().stream().findFirst(predicate);
-
Good:
for (final var chr : characterList) { // Do shit } final var mmo = currMap.getMapObjs().stream().findFirst(predicate);
-
-
In general, use interface types whenever you do specify the type of a local variable, field, or method parameter:
-
Bad:
private final ArrayList<String> guestList = new ArrayList<>(5);
-
Good:
private final List<String> guestList = new ArrayList<>(5);
-
-
Fields should be
final
by default. Obvious exceptions to this rule include fields that are Java primitives (int
,char
,boolean
, etc.) and their wrapper classes (Integer
,Character
,Boolean
, etc.), although even they should befinal
as well if they are constant:-
Bad:
private ThisClass instance = new ThisClass(); private Set<Integer> monsterLevels = new TreeSet<>();
-
Good:
private final ThisClass instance = new ThisClass(); private final Set<Integer> monsterLevels = new TreeSet<>();
-
-
Fields should be
private
by default, orprotected
when appropriate. -
Don't use package-private access level unless you know exactly what you are doing. Instead, explicitly specify the access level of a method or field to be
private
,protected
, orpublic
, as appropriate. -
Don't use
.parallelStream()
unless you know exactly why you are doing it and how it works. Prefer.stream()
instead. -
Do not ignore caught exceptions unless there is an explicit and controlled reason for doing so.
-
When
catch
ing exceptions, catch the most specific exceptions possible; avoidcatch
ingThrowable
orException
unless absolutely appropriate. -
Prefer to return an empty object (e.g. an empty
ArrayList
) over returningnull
when appropriate. -
Instead of handling or returning possibly-
null
("nullable") values, useOptional
. Prefer not to deal directly withnull
values at all, especially in the context of returning them from a method. Good practice is to returnOptional.empty()
when a method has no "good" return value or it fails in some way to produce results, instead of returning the usualnull
:-
Bad:
/** * <ul> * <li>pure?: false</li> * <li>nullable?: true</li> * </ul> */ public ResultObj aMethod(final ParamType foo) { try { return tryToDoThing(foo); } catch (SomeException se) { return null; } }
-
Good:
/** * <ul> * <li>pure?: false</li> * <li>nullable?: false</li> * </ul> */ public Optional<ResultObj> aMethod(final ParamType foo) { try { // Assume here that `tryToDoThing` is not nullable. return Optional.of(tryToDoThing(foo)); } catch (SomeException se) { return Optional.empty(); } }
-
-
Always use type-safe versions of things when they are available, e.g.:
-
Bad:
public Set<SpecificType> dummyMethod() { return Collections.EMPTY_SET; }
-
Good:
public Set<SpecificType> dummyMethod() { return Collections.emptySet(); }
-
-
When implementing a "getter" method that returns some kind of
Collection
, consider the implementation carefully and include a javadoc comment for the method explaining exactly what it returns.-
(In general do not do the following kind of implementation:) If you want callers of the method to be able to directly manipulate the
Collection
that they are "getting," i.e. you want them to have a reference to theCollection
itself, then the method can work like a normal getter but should indicate that it hands off a direct reference (again, don't do this as it breaks encapsulation, very often produces bugs, and is often not what you really want anyways). -
If you want callers to have a view of the
Collection
's contents, real-time (even as they are possibly modified by other threads) but don't want them to be able to modify it (keeping encapsulation and reducing bugs), use the wrapper methods that comestatic
with theCollections
class:public List<SpawnPoint> getOwnSpawns() { return Collections.unmodifiableList(ownSpawns); }
-
If you want callers to get a copy of the
Collection
in the exact state it was in when they called the getter method in order to keep encapsulation and avoid possible concurrency errors(!), just use a constructor method (and make sure the javadoc lets callers know they are not getting a view of the collection). This method, while it is the safest, is the most expensive and thus should be used sparingly (usually the method listed above is better). Callers should be made well-aware that they are making an entirely new collection by copying every single element any time that they call such a method:public List<SpawnPoint> getOwnSpawns() { return new ArrayList<>(ownSpawns); }
-
-
When performing arithmetic that mixes integral types (
int
,long
, etc.) with floating-point types (float
,double
), prefer to cast integral types to floating-point types before doing the operations, and then cast back to an integral type if needed:-
Bad:
final int fooInt = (int) ((barInt - bazInt) * quuxDouble);
-
Good:
final int fooInt = (int) ((double) (barInt - bazInt) * quuxDouble);
-
-
Prefer to use
static
methods (andstatic
classes) over instanced/singleton classes. -
Do not make unchecked assignments with parametrized types, i.e. always specify parameter type:
-
Bad:
final List<String> someStrings = new ArrayList();
-
Good:
final var someStrings = new ArrayList<String>();
-
-
When creating a new
Collection
object, it is generally a good idea to specify the parameters to the constructor unless the default parameters are the most appropriate or it cannot be known what parameters make sense until runtime:-
(Probably) bad:
private final Map<Integer, MapleMapObject> mapObjects = new ConcurrentHashMap<>();
-
Good:
private final Map<Integer, MapleMapObject> mapObjects = new ConcurrentHashMap<>(10, 0.7f, 2);
-
-
Do not use inheritance/extension of objects. Instead, if some kind of "hierarchy" is needed or "supertype" is needed for some set of types, use interfaces ONLY. Just create an interface class (such a class only defines the signatures of functions, and possibly has implementations for
static
/default
methods andfinal static
constants) and then have "subobjects" which are concrete implementations thatimplement
the interface. -
When it's necessary to make explicit type coercions between non-primitive types (hopefully never), try to do so as early on as possible instead of making the coercion immediately before doing something with the new type. This generally should entail encapsulating the coercion and making some class return the value of the already-coerced type from one of its methods.
-
Bad:
// Room.java public List<Furniture> getFurniture() { return new ArrayList<>(furniture); } // Foo.java private void bar(final Room room) { room.getFurniture() .stream() .filter(furniture -> furniture instanceof Chair) .map(furniture -> (Chair) furniture) .forEach(this::doSomethingWithChairs); }
-
Good:
// Room.java public List<Furniture> getFurniture() { return new ArrayList<>(furniture); } public List<Chair> getChairs() { /* * Could also have a field in each class that implements Furniture * that indicates type using an enum value, but `instanceof` directly * checks for what we want. */ return furniture.stream() .filter(furniture -> furniture instanceof Chair) .map(furniture -> (Chair) furniture) .collect(Collectors.toList()); } // Foo.java private void bar(final Room room) { room.getChairs() .forEach(this::doSomethingWithChairs); }
-
-
Whenever possible, make all fields, local variables, and method parameters that are declared outside of and used inside of an anonymous function explicitly
final
. (Fields should already befinal
anyways.) -
Use the strongest access specifier that is appropriate when declaring methods, classes, and fields. Viz., prefer
private
overprotected
overpublic
. -
Methods that are to be called inside of scripts must be
public
, otherwise shit just won't work. -
Do not use reflection, with the slight exception of
instanceof
. -
When doing
String
concatenation many times into a singleString
(i.e. inside of a loop,.forEach()
, or.reduce()
), useStringBuilder
, and make sure not to do any concatenation inside ofStringBuilder
method calls. Call.append()
multiple times instead. -
When dealing with
Collection
instances that are quite volatile and prone to concurrency errors, prefer to use synchronized variants or wrappers to theCollection
, and useIterator
s instead ofStream
s orfor
loops, like so:private final Map<String> visibleNames = Collections.synchronizedSet( new LinkedHashSet<>(35, 0.7f) ); // Here we have two getter methods, for different purposes: /** * Gets an unmodifiable but direct and up-to-date view * of the currently visible names. * * @return Unmodifiable set of visible names. * * @pure: false * @nullable: false */ public Set<String> getVisibleNames() { /* * Here we use a `synchronized` block because it's more * general-purpose, but in the case that the volatile field * in question is the only one that needs this treatment * and you are sure to synchronize all methods with the * capability to modify the collection and you are sure * that this specific instance of this class is the * only object that can directly modify the collection, it's * better to instead synchronize the methods themselves. */ synchronized (visibleNames) { return Collections.unmodifiableSet(visibleNames); } } /** * Gets a snapshot of the currently visible names * at the time that this method is called. * * @return Static set of names. * * @pure: false * @nullable: false */ public Set<String> readVisibleNames() { /* * Notice that this function is called "readVisibleNames" * to distinguish it from "getVisibleNames." */ synchronized (visibleNames) { return new HashSet<>(visibleNames); } } /** * Adds a name to the set of currently * visible names. * * @param name The name to be added * * @pure: false */ public void addVisibleName(final String name) { // We do not use `Iterator` here, since `Iterator`s cannot add. synchronized (visibleNames) { visibleNames.add(name); } } /** * Removes a name from the set of currently * visible names. * * @param name The name to be removed * * @pure: false */ public void removeVisibleName(final String name) { synchronized (visibleNames) { var nameIter = visibleNames.iterator(); while (nameIter.hasNext()) { String nextName = nameIter.next(); if (name == null ? nextName == null : name.equals(nextName)) { nameIter.remove(); break; } } } } /** * Checks if a name is currently visible or not. * * @param name The name to check for * * @pure: false */ public boolean isNameVisible(final String name) { synchronized (visibleNames) { var nameIter = visibleNames.iterator(); while (nameIter.hasNext()) { final var nextName = nameIter.next(); if (name == null ? nextName == null : name.equals(nextName)) { return true; } } return false; } }
-
When using a
Comparator
(e.g. for sorting), use thestatic
methods provided by theComparator
class instead of an explicitly codedComparator
whenever there is one available. -
Do not explicitly box or unbox values unless absolutely necessary (it shouldn't be).