General programming guidelines
This article is part of our Academy Course titled Advanced Java.
This course is designed to help you make the most effective use of Java. It discusses advanced topics, including object creation, concurrency, serialization, reflection and many more. It will guide you through your journey to Java mastery! Check it out here!
Table Of Contents
1. Introduction
In this part of the tutorial we are going to continue discussing general principles of good programming style and robust design in Java. Some of those principles we have already seen in the previous parts of the tutorial, however a lot of new practical advices will be introduced along the way aiming to improve your skills as a Java developer.
2. Variable scopes
In the part 3 of the tutorial, How to design Classes and Interfaces, we have discussed how the visibility and accessibility could be applied to class and interface members, limiting their scope. However we have not discussed yet the local variables, which are used within method implementations.
In the Java language, every local variable, once declared, has a scope. The variable becomes visible from the place it is declared to the end of the method (or code block) it is declared in. As such, there is only one single rule to follow: declare the local variable as close to the place where it is used as possible. Let us take a look on some typical examples:
for( final Locale locale: Locale.getAvailableLocales() ) { // Some implementation here } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // Some implementation here }
In both code snippets the scope of the local variables is limited to the execution blocks they are declared in. Once the block ends, the local variable goes out of scope and is not visible anymore. It looks clean and concise however with the release of Java 8 and introduction of lambdas, many well-known idioms of using local variables are becoming obsolete. Let us rewrite the for-each
loop from the previous example to use lambdas instead:
Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // Some implementation here } );
The local variable became the argument of the function which itself is passed as the argument of the forEach
method.
3. Class fields and local variables
Every method in Java belongs to some class (or some interface in case of Java 8 and the method is declared as default
). As such, there is a probability of name conflicts between local variables used in method implementations and class members. The Java compiler is able to pick the right variable from the scope although it might not be the one developer intended to use. The modern Java IDEs do a tremendous work in order to hint to the developers when such conflicts are taking place (warnings, highlightings, …) but it is still better to think about that while developing. Let us take a look on this example:
public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } }
The example looks quite simple however there is a catch. The method calculateValue
introduces a local variable with name value
and by doing that hides the class member with the same name. The line 08
should have summed the class member and the local variable but instead it does something very different. The correct version may look like that (using the keyword this
):
public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } }
Somewhat naive implementation but nevertheless it highlights the important issue which in some cases could take hours to debug and troubleshoot.
4. Method arguments and local variables
Another trap, which quite often inexperienced Java developers fall into, is using method arguments as local variables. Java allows to reassign non-final
method arguments with a different value (however it has no effect on the original value whatsoever). For example:
public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; }
It is not a beautiful piece of code but it is good enough to reveal the problem: method argument str
is reassigned to another value (and basically is used as a local variable). In all cases (with no exception) this pattern could and should be avoided (for example, by declaring method arguments as final
). For example:
public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; }
The code which does follow this simple rule is much easier to follow and reason about, even at the price of introducing local variables.
5. Boxing and unboxing
Boxing and unboxing are all the names of the same technique used in Java language to convert between primitive types (like int
, long
, double
) to respective primitive type wrappers (like Integer
, Long
, Double
). In the part 4 of the tutorial, How and when to use Generics, we have already seen it in action while talking about primitive type wrappers as generic type parameters.
Although the Java compiler tries to do its best to hide those conversions by performing autoboxing, sometimes it make things worse and leads to unexpected results. Let us take a look on this example:
public static void calculate( final long value ) { // Some implementation here }
final Long value = null; calculate( value );
The code snippet above compiles perfectly fine, however it throws NullPointerException
on line 02
when the conversion between Long
and long
happens. The advice here would be to prefer using primitive type (however as we already know, it is not always possible).
6. Interfaces
In the part 3 of the tutorial, How to design Classes and Interfaces, we have discussed interfaces and contract-based development, emphasizing a lot on the fact that interfaces should be preferred to concrete classes wherever possible. The intent of this section is to convince you one more time to consider interfaces first by showing off real examples.
Interfaces are not tied to any particular implementation (with default methods being an exception). They are just contracts and as such they provide a lot of freedom and flexibility in the way contracts could be fulfilled. This flexibility becomes increasingly important when the implementation involves external systems or services. Let us take a look on the following simple interface and its possible implementation:
public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; }
public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } }
The code snippet above demonstrates a typical interface / implementation pattern. The implementation uses external HTTP service (http://api.geonames.org/) to retrieve the time zone for a particular location. However, because the contact is driven by the interface, it is very easy to introduce yet another implementation using, for example, database or even flat file. With that, interfaces greatly help to design testable code. For example, it is not always practical to call external service on each test run so it makes sense to provide the alternative, dummy implementation (also known as stub or mock) instead:
public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } }
This implementation could be used in every place where TimezoneService
interface is required, isolating the test scenario from dependency on external components.
Many excellent examples of appropriate use of the interfaces are encapsulated inside the Java standard collection library. Collection
, List
, Set
, all those interfaces are backed by several implementations which could be replaced seamlessly and interchangeably when contracts are favored, for example:
public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } }
print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) ); print( new Vector< Long >( /* ... */ ) );
7. Strings
Strings are one of the most widely used types in Java and, arguably, in most of the programming languages. The Java language simplifies a lot the routine operations over strings by natively supporting the concatenations and comparison. Additionally, the Java standard library provides many different classes to make strings operations efficient and that is what we are going to discuss in this section.
In Java, strings are immutable objects, represented in UTF-16 format. Every time you concatenate the strings (or perform any operation which modifies the original string) the new instance of the String
class is created. Because of this fact, the concatenation operations may become very ineffective, causing the creation of many intermediate string instances (generally speaking, generating garbage).
But the Java standard library provides two very helpful classes which aim to facilitate string manipulations: StringBuilder
and StringBuffer
(the only difference between those is that StringBuffer
is thread-safe while StringBuilder
is not). Let us take a look on couple of examples using one of these classes:
final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" );
Though using the StringBuilder
/ StringBuffer
is the recommended way to manipulate strings, it may look over-killing in simple scenario of concatenating two or three strings so the regular + operator could be used instead, for example:
String userId = "user:" + new Random().nextInt( 100 );
The often better alternative to straightforward concatenation is to use string formatting and Java standard library is also here to help by providing the static helper method String.format
. It supports a rich set of format specifiers, including numbers, characters, dates/times, etc. (for the complete reference please visit the official documentation). Let us explore the power of formatting by example:
String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12%
The String.format
method provides a clean and easy approach to construct strings from different data types. It worth to mention that some modern Java IDEs are able to analyze the format specification against the arguments passed to the String.format
method and warn developers in case of any mismatches detected.
8. Naming conventions
Java as a language does not force the developers to strictly follow any naming conventions, however the community has developed a set of easy to follow rules which make the Java code looking uniformly across standard library and any other Java project in the wild.
- package names are typed in lower case:
org.junit
,com.fasterxml.jackson
,javax.json
- class, enum, interface or annotation names are typed in capitalized case:
StringBuilder
,Runnable
,@Override
- method or field names (except
static final
) are typed in camel case:isEmpty
,format
,addAll
- static final field or enumeration constant names are typed in upper case, separated by underscore ‘_’:
LOG
,MIN_RADIX
,INSTANCE
- local variable and method arguments names are typed in camel case:
str
,newLength
,minimumCapacity
- generic type parameter names are usually represented as one character in upper case:
T
,U
,E
By following these simple conventions the code you are writing will look concise and indistinguishable from any other library or framework, giving the impression it was authored by the same person (one of those rare cases when conventions are really working).
9. Standard Libraries
No matter what kind of Java projects you are working on, the Java standard libraries are your best friends. Yes, it is hard to disagree that they have some rough edges and strange design decisions, nevertheless in 99% it is a high quality code written by experts. It is worth learning.
Every Java release brings a lot of new features to existing libraries (with some possible deprecation of the old ones) as well as adds many new libraries. Java 5 brought new concurrency library reconciled under java.util.concurrent
package. Java 6 delivered (a bit less known) scripting support (javax.script
package) and the Java compiler API (under javax.tools
package). Java 7 brought a lot of improvements into java.util.concurrent
, introduced new I/O library under java.nio.file
package and dynamic languages support with java.lang.invoke
package. And finally, Java 8 delivered a long-awaited date/time API hosted under java.time
package.
Java as a platform is evolving and it is extremely important to keep up with this evolution. Whenever you are considering to introduce third-party library or framework into your project, make sure the required functionality is not already present in the Java standard libraries (indeed, there are many specialized and high-performance algorithm implementations which outperforms the ones from standard libraries but in most of the cases you do not really need them).
10. Immutability
Immutability is all over the tutorial and in this part it stays as a reminder: please take immutability seriously. If a class you are designing or a method you are implementing could provide the immutability guarantee, it could be used mostly everywhere without the fear of concurrent modifications. It will make your life as a developer easier (and hopefully lives of your teammates as well).
11. Testing
Test-driven development (TDD) practices are extremely popular in Java community, raising the quality bar of the code being written. With all those benefits which TDD brings on the table, it is sad to observe that Java standard library does not include any test framework or scaffolding as of today.
Nevertheless, testing becomes a necessary part of the modern Java development and in this section we are going to cover some basics using excellent JUnit framework. Essentially, in JUnit, each test is a set of assertions about the expect object state or behavior.
The secret of writing great tests is to keep them short and simple, testing one thing at a time. As an exercise, let us write a set of tests to verify that String.format
function from the section Strings returns the desired results.
package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } }
The tests look very readable and their execution is instantiations. Nowadays, the average Java project contains hundreds of test cases, giving the developer a quick feedback on regressions or features under development.
12. What’s next
This part of the tutorial finishes the series of discussions related to Java programming practices and guidelines. In the next part we are going to return back to the language features by exploring the world of Java exceptions, their types, how and when to use them.
13. Download the Source Code
This was a lesson on the General programming guidelines, lesson of Advanced Java course. You may download the source code here: AdvancedJavaPart7