Skip to main content

Java Best Practices

Java Best Practices
Java Best Practices
(Beyond the Basics)

Some of the practices are JDK version specific and some are are general best practices. You will also find few tips to solve common problems using Java Language and useful third party open source libraries as well.

Java 8 brings host of new features and programming paradigms to developer world. It is the most exciting upgrade of Java ever since HotSpot compiler was debuted. If you want to take a trip down memory lane when every major vendor was putting their best brains into making Java faster, check out this edition network world from 1998.

Let's dive into some of the best practices and common misconceptions encountered while using Java and more specifically Java 8. You can find basic concepts of Java 8 and Lambda here.These best practices are not fully elaborated. I encourage all readers to pick up these hints and do own research.

All the sample code demonstrating these best practices can be found at : https://github.com/mydevco/codeshare/blob/master/src/com/mydevco

Language Constructs, Data Types
  •   Prefer primitive types over wrapper types as much as possible. Often programmers use wrapper types such as Integer, Double in place of int or double in order to handle the case when the there is no value set. The default value for wrapper type is null whereas primitive types are mostly initialized to 0. Autoboxing though a convenient feature could cause performance issues. If the conversion is being done in computationally intensive operation then you should prefer primitives. Arrays or primitives would yield better performance rather than using Collections which mandates wrapper types only and ultimately forcing boxing and unboxing when dealing with primitives.

    One workaround to default value problem is to initialize variables with special types such NaN (Not a number). The getter methods of JDBC return a default value if the underlying column is null. In order to distinguish between actual zero vs the null in the database, you can use getObject or wasNull method of ResultSet. Wrapper class variable can be used in data objects for columns which represent nullable in db. Using primitives for not nullable columns also provides a safety net and compile time check.
  •  Use shortest possible data type for your domain model. If an int or float suffice the need, you should not blindly use long or double. For in memory processing of large data, you should scrutinize the choice of each member type (non local variables). Switching for int to short or string to char array might make your logic more complex but it would save lot of memory if the number of instances of a particular classes are in thousands at a given time.
  •  There is no precision based rounding in Java. Java's Math.round simply rounds to nearest integer. However a simple trick to achieve excel like precision based round is to multiply with Math.pow(10,precision) , round and then divide by same Math.pow(10,precision)

    double roundedFor4Decimal = Math.round(x * Math.pow(10,4))/Math.pow(10,4);
  •  Applying division could result in unwanted downcast even though the assigned variable is a float or double. To avoid such issue make one of the operand float.
    For example double x = 1 / 20; // result would be 0 not 0.05 instead use 1.0/20 or 1/20.0
  •  Using float or double value directly in strings is not a good idea as after certain precision Java would show the number in exponential form which is hard to decifer for end users. Use NumberFormat or DecimalFormat to create user friendly display string for decimals. NumberFormat gives more control on locale specific symbols like thousand seperator and decimal seperator.

    System.out.println("Unfriendly exponential number is " + (5.0/1000000)); //5.0E-6
    System.out.println("Friendly exponential number is " + new DecimalFormat("0.0").format(5.0/1000000)); //.000005

    If you need to take formatter pattern of excel and replicate the result in Java, you can take help from apache POI library class , HSSFDataFormat.

    DataFormatter df = new DataFormatter();
    String excelFormatString = "$#,##0_);($#,##0)";
    System.out.println(df.formatRawCellContents(-12345.8956734, HSSFDataFormat.getBuiltinFormat(excelFormatString), excelFormatString)); //prints ($12,346)
  •  To check for finite, infinite and Nan, use static methods in Double class e.g Double.isNaN or Double.isFinite.
  •  If one of the operand is NaN then the resulting value is also NaN. There is no such thing as -NaN.
  •  If one of the operand is of type short, char or byte then the result is promoted to int when using unary operator (++ or --).
  •  If one of the operand is of type double then other operand is promoted to double when using binary operator (+, /, *, -). Similarly for float or long, other operand is promoted to float/long. All other types get converted to int.
  •  The following rules dictate result of conditional operator, if one of the operand after "?" is :
       - byte and short result in byte
       - byte/short or char and constant int value (e.g 4567) result in type of first operand (byte/short or char)
  •  When a method is doing auto unboxing from the wrapper variable passed as an arguement, the method must check for null. A null value if accidentally used for unboxing would through NPE.
  •  Pass by Value vs Pass by Reference (Refer to example code JavaPassRefVal.java in example code above).
     - Java uses Pass By reference when a reference is sent to method. The method could change the value pointed though the reference. However the method can not polute the reference (address) itself by assigning it to somewhere else.
     - Java uses copy of the value when a primitive is passed as an arguement. The method could change the value but the change would not reflect the original primitive variable.
  •   Dealing with stack memory is more efficient than leaving the garbage collection to GC. Try to use local variables wherever possible. It will also help with any unwanted pollution of values. Avoid package access modifier which is default modifier. Package private is less restrictive than private.
  •  Many developers don't realize that Java's multi dimensional array is infact a ragged or variable dimension array. What it means is that each array member of the multidimensional array could be of variable length. Please refer to the example code VarDimArray.java
  • When you add, substract or multiply two numbers with +, - or * operator in java, the result could be unexpected if it goes beyond the boundaries of intger or long data type. This is problem specially when the addition is happening in a long iteration or recursion. Usually the number gets rolled over to the opposite sign and end result becomes a junk value. Use Math.addExact, Match.substractExact or Match.multiplyExact functions to get overflow exception in such cases.
  •  Enumeration values can be directly compared using == operator. You don't have to use equals.
  •  Object's clone method returns Object, hence cast is needed after cloning. Cloning is a shallow copy so underlying object members are not copied. Prefer copy constructor over cloning when possible. When implementing deep cloning, always create a new member instances instead of simply returning new references as it could result in various side effects. Similarly when implementing equals, you should invoke equals of all instance types and leave the individual equal implementation to those classes.
  •  Java enum is a singleton class which can have constructor, methods and data members. Enums should be used to provide meaningful names to constant numbers which can be used in easy comparisons. I have provided a sample enum usage in EnumExample.java.
  •   If you override equals, you need to override hashcode method as well. A common misconception is that we need to use all the properties of the class in equals and hashcode. You need to use only the ones which makes the class unique. This is tricky when you are modeling a POJO against a database table which has a primary key say long id and three other properties in the table (first name and last name and date of birth). If you take up the responsibility to make sure the id is always populated when an instance of this class say Citizen, is created then id is enough to implement equals and hashcode. However if you may have many instances of Citizen who are going to get their id only when they are being persisted, then id may not be the right choice for quals/hashcode. Hence a smart equals and hashcode method should use id, firstname, lastname and dateOfBirth. You can go crazy and use the auto code generation feature of eclipse and pull every field like address, age, height, weight, alias, salution etc in equals/hashcode but that would slow down all the collections operations which compares hashcode to retrieve elements for operation such as delete. A typical implementation of hashcode could involve adding hashcode of all the low level properties.

    Equals and Hashcode contract:
    - Within the boundaries of single execution, hashcode should return same int for same object across multiple invocation.
    - If equals is true on two object then their hashcode should also match.
    - If equals returns false then hashcode should be different to avoid collisions and when using the object as key in maps. It is not a contract but a best practice.
    - Eclipse generates hashcode using constant number along with odd prime 31 and and equals using the standard practice and it should work for majority of use cases. In case you need to write your own hashcode using a alternative algorithms, there are many options such as fasthash or murmur available with google's guava library. You may refer to my blog post on hashcode fundamentals here.
  •   While defining your own annotations, create different combinatination of arguements which goes together. If you dump all arguements into one annotation interface and expects user to send null for unused one, the handling of annotation would be cumbersome. Also it would be unneccesarily lengthy even if the consumer of annotation want to use only few arguements from the list.
    
       @Retention(RetentionPolicy.RUNTIME)
       public @interface CustomAnnotation {} // Marker
       public @interface CustomAnnotation {
        String firstArguement();
       } // Single value
       public @interface Feedback {
        String firstArguement();
        String secondArguement() default "None";
       } // Multi value
      
  •  Functional Interface is new concept in Java 8 which makes it possible to pass function handle to method. Any interface becomes functional interface (also known as SAM or Single Abstract Method). Alternately you may use the annotation @FunctionalInterface. There could be any number of default methods in functional interface. Java 8 introduced multiple inbuilt Functional interfaces such as Camparator, Runnable, Callable, Function etc. I prefer using functional interfaces to keep lambdas clean instead of dumping the function body within lambda predicate. Unless the body is less that 3 lines, you should try to create functional interfaces. FunctionalInterfaceExample.java

     - IDEs like eclipse could not decipher the definition of functional interface while in debug mode. Instead, if you try to evaluate the expression, it will be shown as memory address where Functional interface is stored.
     - One important limitation of the default method is that you can not override methods from java.lang.Object as default methods although it seems trivial to put methods like toString, equals or hashcode as default. The answer to this question is well explained by the java author himself in the post.
OOP, Statements and Exception Handling
  •   Java now supports strings in switch statements. However string comparisons are slower than numbers. If you have nested switch statements or performance needs to optimal then prefer if/else with string.equals or enum for swtich. You can check NestedSwitch.java example for a demostration of which one is better performing.
  •  AutoClosable interface was introduced in Java 7. It allows us to write less verbose try catch block while making sure all the resources are closed when the try block is over (without explicitly using finally). The JDK classes in File I/O and JDBC are already ehanced to implement AutoClosable. While implementing close method of AutoClosable, developer should throw more specific exception. However throwing InterruptedException could have side effect hence strongly discouraged. Defining your own autoclosable classes will allow you to exploit this feature for lesser code.
    example: Here file writer instance will be automatically closed before control moves beyond closing braces.
    
       try (FileWriter f = new FileWriter("\\myfile.txt"))
       {
       f.write("example from mydeveloperconnection.com");
    }
  •  Always define no-arguement constructor explicitly unless there is specific need to avoid one. No-arg constructor is used by majority of the dependency injection frameworks to instantiate the class. Also you lose the implicit empty constructor when there is constructor with atleast one arguement. This put additional burden in subclasses to call specific constructors. For all practical reasons, it is more convenient and cleaner approach to allow the consumer of the class to start with empty constructor and fill the properties as they become available. This leads to another practice of defining default values as variable assignments instead of putting all default values in default constructor. The no arguement constructor should be empty or do some initialization task such as instantiating connection or checking mandatory dependency for the existance of class instance.
  • Unlike protected member variables, you don't need to inherit a class to access its package private properties (as long as both belong to same package). Hence any class can pollute the member variable by simply declaring the package to same. I would recommend to avoid package private modifier altogether.
  • Var-args (variable length arguements denoted with ellipses ...) are shortcut to declaring array input arguement for a method. It is advised to always use variable arguements along with a one or more arguement for default values when there is no input for var-args. Java has a nice example for this best practice:
    public PrintStream printf(String format, Object... args)
    Example : System.out.printf("MDC welcomes %s%n for %d%nth time", "Visitor", 10);
  • When defining your own application specific exceptions, you should have provide at least two constructors. No-arg constructor and one with a string message and a throwable to wrap the exception around. To avoid losing the message of underlying exception, the decorator string arguement should be prefixed with throwable.getMessage. Also such exception can either elaborate on the cause or simply set the cause of passed in throwable into user defined exception.
  • Java exception uses It is not a built-in java feature but you can reduce the stack trace for common exception in log4j by using EnhancedPatternLayout as %throwable{n} for showing n lines of stacktrace.
  • Java's date and time api was lacking several key features for a long time. Java 8 added several userfriendly classes and packages under java.time. Familiarize yourself with key classes java.time.Clock, java.time.Duration, java.time.instant, java.time.Period and java.time.temporal. For display formatting of time you can use java.time.format.DateTimeFormatterBuilder and java.time.format.DateTimeFormatter classes. Javadoc is quite self sufficient on this topic. The reason I mentioned these classes here is that you should not ignore them and use the hard way to deal with date and time (using Date, Calendar..). Please refer to NewDateTimeExamples.java
Serialization, Input and Ouput
  • Java 7 introduced a utility class java.nio.file.Files with many useful abstraction over file I/O. They are mostly threadsafe and weekly consistent so underlying file system is not locked. You can find several examples using lambda in . Here are few interesting methods of Files class :
    
       static Path copy(Path source, Path target, CopyOption... options)
       static Path createTempDirectory(Path dir, String prefix, FileAttribute... attrs)
       static Path createTempFile(Path dir, String prefix, String suffix, FileAttribute... attrs)
       static boolean exists(Path path, LinkOption... options)
       static Stream<path> find(Path start, int maxDepth, BiPredicate<Path,BasicFileAttributes> matcher, FileVisitOption... options)
       static UserPrincipal getOwner(Path path, LinkOption... options)
       static Stream<String> lines(Path path)
       static Stream<Path> list(Path dir)
       static long size(Path path)
       static Stream<Path> walk(Path start, int maxDepth, FileVisitOption... options)
       static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)
      
    I have created a handly example using most of the Files utility functions at FilesIOExamples.java
  • Use glob syntax to filter directory for a pattern or file type. Example code : FileFilterGlobExample.java
    
       Path dir = Paths.get("data");
       try (DirectoryStream<Path> stream =
            Files.newDirectoryStream(dir, "*.{csv,txt}")) {
           for (Path entry: stream) {
               System.out.println(entry.getFileName());
           }
       } catch (IOException x) {
           System.err.println(x);
       }
      
  • Java Serialization has two important yet less known methods which could change the behavior of serialization and deserialization completely. Developers should under the impact and order of execution of writeReplace, writeObject, readObject and readResolve methods. These methods can be private or protected but ObjectInputStream and ObjectOutputStream magically invokes them in a particular order to allow the consumer or producer of the object to alter the results.

    During serialization these methods are invoked in the order given below:

    1.  private Object writeReplace() throws ObjectStreamException
    2.  private void writeObject(java.io.ObjectOutputStream out)

    During de-serialization these methods are invoked in the order given below:

    1. private void readObject(java.io.ObjectInputStream in)
    2. private Object readResolve()
    3. public void validateObject()

    To make sure deserialization does not create multiple copies of singleton classes, you should query the instance of singleton, if available in jvm and return the same in readResolve method.

    To have a better and clear control, prefer using writeExternal and ReadExternal override from Externalizable interface instead of markup interface Serializable which could use proxy object replacements.

    An intersting read on java's serialization could be found at http://www.ibm.com/developerworks/library/j-5things1/
    http://www.ibm.com/developerworks/library/j-5things1/
  • Serialization was one of the major roadblocks to push Java 8 Lambda enhancements out of the door. Serialization of lambda expression is highly discouraged by Oracle as of Java 8. It could change in future. Lambda expressions can be serialized if its target type and arguements are serializable. Same is the case with inner classes also. To make LambdaSerializable Java 8 uses java.lang.invoke.SerializedLambda to preserve the intention and properties of the author of lambda. It can be utilized by runtime libraries to overwrite the default deserialization by returning SerializedLambda from writeReplace method as a deserialized proxy of the original lambda. SerializedLambda's readResolve method invokes deserializeLambda method of recieving class or deserialized lambda. If you are implementing deserializeLambda method in your lambda class then you are responsible for consistency of lambda at both ends.
  • To enforce a generic class to be serializable, use extends instead of implements. "Implements" is not supported inside <>
    public class MustBeSeriazableClass<T extends Serializable> {...}
    An example code for serialization with generics can be found here.
Garbage Collection
  • Analyze GC throughput and application pause characterstics using analalysis tools such as "Youkit/JProfiler (commercial)", VisualVm", "Jstat", "jconsole", "jhat", "jmap", "HProf Profiler". I found GCViewer and Netbeans Profiler quite useful. The biggest challenge of optimizing Garbage Collection throughput comes when dealing with large heap. If you are comfortable with G1 collector, this video from spark summit provides a good insight on intel engineer's experience with G1. Since there are so many official and experimental options in JVM for garbage tuning, it remains a puzzle as where does one start with? Well, you could start with -Xmx and -Xms and CMS collector and play around with few hints at a time. For a heap between 4-32GB, these options may not be stable but well worth to try if the current performance does not meet your expectations:

    
      -server : enhances jvm performance for servers
      -XX:+PrintGC or -verbose:gc
      -XX:+PrintGCDetails
      -XX:+PrintGCTimeStamps
      -XX:-PrintConcurrentLocks
      -XX:+UseParNewGC - can be used with low pause CMS
      -XX:+UseConcMarkSweepGC - use CMS collector
      -XX:+CMSConcurrentMTEnabled - concurrent CMS phases are run with multiple threads (active by default, to disable use -CMSConcurrentMTEnabled)
      -XX:+UseG1GC
      -XX:MaxGCPauseMillis=[time in ms] - hint for max gc pause
      -XX:MinHeapFreeRatio=[value in percent] - target minimum percent value of free to total heap size (40 is default)
      -XX:MaxHeapFreeRatio=[value in percent] - target maximum percent value of free to total heap size (70 is default)
      -Xmn[bytes] - size of young generation. Use only if there is lot of short lived objects in the applications
      -XX:+HeapDumpOnOutOfMemoryError - used for collecting heap dump 
      -XX:HeapDumpPath=[heap dump path]
      -XX:SurvivorRatio=n - Ratio of eden/survivor space size (default is 8)
      -XX:MaxTenuringThreshold=n - Maximum value for tenuring threshold (default is 15)
      -XX:CMSInitiatingOccupancyFraction=[percent value] wait for old generation heap space before starting CMS cycle. Default is 68%
      
    NeNever try to optimize something which doesn't seem to be a problem. Although your intentions might be to get the best performance and you might end up with worse performance in production environment. Secondly the parameter values used in development environment and development box may not yield the same result in production enviurionment when though heap specially if you are using experimental settings. Do try all the settings in comparable environment with projected load for 2-5 years ahead. Finally there is no magic numbers to copy paste and use in your application. Try not to deviate far from defaults in JDK. Distributed JVM are even more complex to tune.
  • Permgen memory space is replaced by memory space called Metaspace in Java8. Which means there won't be any "java.lang.OutOfMemoryError: PermGen space " error. Metaspace can grow as much as OS allows out of native memory. You may still use MaxMetaspaceSize flag to limit this memory. To analyze the metspace dynamic resizing, you can take a look of the keywords "Metadata" and Metaspace" in verbose gc output. In 99% of cases you don't have to deal with metaspace.
  • When overriding object's finalize() method, don't forget to include super.finalize().
  • Explicit invocation of system.gc() or Runtime.getRuntime().gc() could trigger full gc and should be invoked during low usage or for maintenence.
Lambda Expression, Collections and Streams
  • The foundation of lambda expression lies in the brand new bytecode instruction introduced in Java 7, invokedynamic. Its used to make method calls. Instead of relying on compile time linking, VM  utilizes new invokedynamic entries added to bycode. These entries rely on BSM (bootstrap methods) static entries to identify the signature and name and return type of the method. BSM returns CallSite which contains MethodHandle. Once invokedynamic linking is made to MethodHandle, method can be invoked. You can use MethodHandle (available in Java7) or java.util.Function (in java8) as an alternate to reflection for calling methods indirectly.
     - invokeVirtual is for instance methods.
     - invokeStatic is for static methods. It helps determining method calls based on compiletime information.
     - invokeInterface is for method dispatch via interface.
     - invokeSpecial is for exact dispatch.
     - invokeDynamic start without the target, goes through linking process. InvokeDynamic instruction allows to difer lambda expression implementation specific detail to vm writers without losing the backward compatibility promise of Java. Linking is a onetime process and linked implementation is cached for performance.

    To find out the opcode used in the class you can use javap (disassembler) command with the generated class. It clearly shows as which methods are assigned invokeVisrtual, invokeSpecial and invokeDynamic instruction based on the usage. A good read for lambda transaltion strategy can be found here.. Prefer methodhandle.invoke instead of invokeExact as invoke takes care of boxing, unboxing and type widening if needed. InvokeExact will throw exception if the arguement and returnType does not match exactly. Please refer to sample code InvokeDynamicExample.java
  • Lambdas have target typing capability so it can infer the arguement type based on the expected type of the lambda. Sometimes when there is ambiguity in the arguement or return type, you will have to explicity cast it. It is also true for generics used in lambda. Please refer to the example code TargetTyping.java
  • Capturing lambda can access non static variables and references outside lambda body, if they effectively final (not modified, assigned only once). As best practice you should declare all the variables being used in lambda but declared outside as final. If the variable is not modified outside lambda but getting reassigned to some other variable within lambda, you will get compile time error but adding final keyword will help you catch this issue early and also provide a better readability.
  • Non capturing lambdas are better performing because they evaluated once and reused through the execution. Stateless lambda or non capturing lambda uses static final variable in generated bytecode to point to lambda function.
  • Use memoization technique to reduce number of iterations in recursion. Memoization involves storing tempraryn results of recusion is some sort of table. An example could be found at Memoization.java
  • Many of the standard util classes in java have been enhanced to return Streams. For example Pattern class now has splitAsStream function to be used directly in lambda. Also any function which rerturns a collection are good candidates to start your lambda function. Please refer to FileWordCount.java for an example for both the best practices.Random class has new functions to generate random streams. An example could be found at Random.java
  • AllInOne.java class has sample code to demonstrate all the below best practices:
    - Use Comparator.comparing along sorted function of stream to transform one list to another in a sorted manner. Also check out another example to sort baby names at Exercise1.java
    - Use stream.distinct to remove duplicated. Previously we used to rely on set or map to filter out duplicates.
    - Use stream.anyMatch to check for existence of some entry which satisfy a particular condition. This can be also done as for loop and break statements but lambda function anyMatch is much more efficient.
     - Combine stream reduction function with mathematical functions such as Integer.Max or Math.add to derive single value from a collection of values. This combination of reduce function with a final narrowing function is a shorthand form of map-reduce, supported out of the box in java.
  •  Currying is an important functional programming concept which is now available with Java's lambda. Currying acts as a factory for functions and allows functions to be used as data structures. Refer to a usage example, Currying.java
  • You can apply reduction in a stream as a chain funtion using Predicate::and. Exercise2.java c creates list of predicates and combines them using and operation.
  • Parallel Sorting can be done on Arrays using lambda. Collection does not support parallel sorting. Comparator has a composition ability to chain one after another. Lambda1.java has a example to demonstrate both of these features. They help you keep the code less verbose and small.
  • Running a small block of code does not require Runnable anonymous class anymore. Use lambda to fire a block of code over a thread.
    
        double[][] data = {{1, 3}, {2, 5}, {3, 7}, {4, 14}, {5, 11}};
             SimpleRegression regression = new SimpleRegression();
             regression.addData(data);
     
             new Thread(() -> System.out.println("Interepts : " + regression.getIntercept())).start();
     
             new Thread(() -> System.out.println("Slope : " + regression.getSlope())).start();
     
             new Thread(() -> System.out.println("Standard Error : " + regression.getSlopeStdErr())).start();
      
  • Utilize stream.peak only for debugging or tracing purpose. Streams are lazily evaluated hence the order is not certain.
    
         Stream.iterate(5.0, p -> p * 2)
                    .peek(e -> System.out.println("Fetching " + e)) // peek is a good tool for debugging
                    .limit(5).toArray();
      
  • Use lambda as an replacement for basic command pattern where you need to invoke same method in different command classes. Refer to the example code here.
  • Unit testing lambda can be difficult because of dynamic linking of lambda types. Consider extracting complex lambda expressions into utility methods and write unit test for them seperately.
  • Be aware of the limitations of the lambda expressions. As of Java 8, there are as follows:
     - It is not possible to throw any exception within lambda.
     - There is no equivalent of break statement in forEach method of Stream.
     - Streams can not be directly converted into a collection without a terminal operation. If you are not having any method call to collect or match then you would need to use special method "boxed" before capturing the remaining items of stream into a collection.
  • Map got four useful methods added in Java8, which will save several if statements for you. Use them whenever you can.
     - map.putIfAbsent(key, new value) to add only if the key does not exists.
     - map.computeIfAbsent(key, Function<? super K,? extends V> mappingFunction) to use a lambda function to derive and put value. e.g map.computeIfAbsent(50, key-> 100);
     - map.computeIfPresent(key, BiFunction<? super K,? super V,? extends V> remappingFunction) to apply a lambda function which has access to current key and value to derive new value. This method is useful to create running average/sum or apply some kind of aggregation on existing keys cumulatively. e.g map.computeIfPresent(10, (key, val) -> val + 20);
     
    - map.compute(key, BiFunction<? super K,? super V,? extends V> remappingFunction) similar to computeIfPresent. The value will be passed as null if it does not exist. e.g map.compute(10, (key, val) -> val==null?val : val + 20);
     - map.getOrDefault(42, 100) to return either the value in map or some default value.
Concurrency
  • Best resource for concurrency in Java is the book "Java Concurrency In Practice" by Brian Goetz and Tim Peierls. The code listing is available at https://github.com/jcip/jcip.github.com/blob/master/listings
  • Stream's parallelStream function uses fork join with a jvm wide common forkjoin pool. It automatically distributes the load based on available processors. You can wrap the stream call in individual forkjoin pool as a workaround by explicitly calling submit on those pools. I would recommend using fork join framework directly instead parallelStream if you see any issue with common forkjoin pool. Read more in details about possible side effects of parallelStream here. You can find parallel stream examples at LambdaParallel1.java and LambdaParallel2.java
  • Thread and Runnable can throw exception or return any results. Callable allows us to return values from a thread. Callable has call function instead of run in Thread/Runnable. Instead of adding special construct to wait for the completion of callable, we can submit a callable wrapped inside a Future and call get on future which is a blocking call. You can now submit multiple futures within ExecutorCompletionService and call take() iteratively to get the future as and when they complete (blocking if none are complete). Refer to the example code Renderer.java.
  • CompletableFuture is a new feature in Java8. It provides a callback functionality on top of fork join pool. CompletableFuture implements CompletionStage. Multiple threads could compete to complete or cancel a CompletableFuture and the framework would resolve one to win. An example code could be found here. Key methods of completablefuture class are as follows:
     - If there are two equally effective implementation but not sure which would finish first, use aceptEither
     - Use allOf or anyOf to compose CompletableFuture from other CompletableFuture instances. allOf would block untill all the CompletableFutures are complete. AnyOf would wait until any one is finished (as the name suggests).
     - complete, completeFuture or completeExceptionally allows user to mark a completableFuture complete with a hardcoded value or exception.
     -  The simplest form of submiting a task as CompletableFuture and execute under common ForkjoinPool.commonPool is runAsync or supplyAsync.
     - To create a chain of CompletableFuture such that one could utilize result from previous CompletableFuture, use thenAccept or thenAcceptAsync
  • Synchronized method or block implicitly handles locks for a set of statementsm or a method. However to obtain fine control over lock, one could utilize various implementations of Lock or ReadWriteLock interfaces in Java. ReadWriteLock is similar to pair of Locks (one for read and one forn write). Reentrant lock provides better performance for situation when previous owner of lock can re-acquire the lock if no other thread has done it. Reentrant locks are very useful for recursive operations. Example code.
  • Java 8 introduces new type of Lock called StampedLock which is an enhancement of ReadWriteLock. It has three locking methods - readLock, writeLock and tryOptimisticRead. Also it allows to convert a read lock into write lock.
  • PriorityQueue uses natural order based on priority heap based on comparator given in construictor. It does not allow nulls. This class is not synchronized. To use PriorityQueue in threadsafe manner use PriorityBlockingQueue.
  • BlockingQueue in general blocks retrieval methods untill there is atleast one element in queue. The insert methods are blocked until there is space available (default capacity is Inrteger.Max_Value). put and take are blocking methods. add/remove/element methods throws exception and you can specify timeout with offer and poll. ArrayBlockingQueue is simple to use for common scnerios where the queue is constantly updated and consumed but we need to block when it becomes empty.
  • AtomicInteger provides threadsafe way to increment and decrement values. Similarly AtomicReference provides thread way to reassign references. Java 8 also has AtomicStampedReference where the stamp is an integer which is updated automatically based on the successful operation. There are many classes in java.util.concurrent.atomic package provided as a toolkit for conditional thread safe updates without worrying about explictly handling the locks.
Miscellaneous Best Practices

 It may seem quite trivial to talk about "naming convention" as it is mostly a matter of common sense and avoid redundant verbosity. But many developers tend to ignore its importance. Here are some examples of good and bad ones:

boolean isTrue - Such names will confuse everyone and lead to unnecessary arguements.

int Amount - primitives should have only lower case

public class AddressVO or OrderTO - Instead of repeating VO/TO suffix in all the classes, put these classes into a package which ends with .vo

public class EventObject - Naming a class as object seems contradictory. Another common mistake beginner programmers do is to append the word class in classname e.g EventClass or ShoppingClass.  Unless class refers to some kind of classification in your domain, it causes confusion for the readers.

public class MessageBuilder - This makes sense as the suffix Builder gives some indication of the design pattern and intended behavior. Other examples of such naming convention are RequestHandler, RequestProcessor, OrderCommand, ObjectFactory

Try not to mix interfaces and implementation under same package. Keeping all implementation into a package ending with .impl is much convenient for some one trying to understand the code for the first time.

Divide package into common, core, web, integration, ext (extension), api, util are good way to package individual modules. With Java 9 you will have capability to strip JDK into osgi modules and optimize on the size of library needed. However try not to create packages just to look your codebase look big or unenecessary attempt to organize code. If just two or three packages are sufficient, let be it. Having less number of packages does not mean its a poor design.

  Class names should be nouns, interfaces should be adjectives and method names should be verbs.

  For generics, the recommended convention is to use T for types, E for collection elements and S is used for service loaders. K and V is used as key and value for generic maps.

  Constants should be all upper case. Underscore should be used in currency literals to seperate thousands.

  Enumeration names could be mixed case but the enumeration elements looks good with all upper case. Package names should be unique and start with in reverse order of top level domain name.

Use team or organization wide common code formatter, template, importer rules. Use a set of static code analysis tools such as findbugs, coverity, sonar, JArchitect, PMD or CAST. Netbeans has a decent built-in static code analaysis feature. The key part of using such tools is that the developer community should find it helpful not annoying. If the rules set in the tool is too lame or you are consistently not use some of the standards in order to achieve high response time or small footprint then it should be added to exception to the rule set so it does not create so much noise that everyone starts ignoring them. Sometimes the tools are lacking lambda or java8 feature support and mark them as issues.

 

 


End of Page.