DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Trending

  • AI, ML, and Data Science: Shaping the Future of Automation
  • Rethinking Recruitment: A Journey Through Hiring Practices
  • Docker Model Runner: Streamlining AI Deployment for Developers
  • Microsoft Azure Synapse Analytics: Scaling Hurdles and Limitations
  1. DZone
  2. Coding
  3. Java
  4. Guidelines for Java Code Reviews

Guidelines for Java Code Reviews

Get a jump-start on your next code review session with this list.

By 
Meenakshi Dhanani user avatar
Meenakshi Dhanani
·
Apr. 14, 21 · Opinion
Likes (9)
Comment
Save
Tweet
Share
8.5K Views

Join the DZone community and get the full member experience.

Join For Free

Having another pair of eyes scan your code is always useful and helps you spot mistakes before you break production. You need not be an expert to review someone’s code. Some experience with the programming language and a review checklist should help you get started. We’ve put together a list of things you should keep in mind when you’re reviewing Java code. Read on!

1. Follow Java Code Conventions

Following language conventions helps quickly skim through the code and make sense of it, thereby improving readability. For instance, all package names in Java are written in lowercase, constants in all caps, variable names in CamelCase, etc. Find the complete list of conventions here.

Some teams develop their own conventions, so be flexible in such cases!

2. Replace Imperative Code With Lambdas and Streams

If you’re using Java 8+, replacing loops and extremely verbose methods with streams and lambdas makes the code look cleaner. Lambdas and streams allow you to write functional code in Java. The following snippet filters odd numbers in the traditional imperative way:

Java
 




xxxxxxxxxx
1


 
1
List<Integer> oddNumbers = new ArrayList<>();
2
for (Integer number : Arrays.asList(1, 2, 3, 4, 5, 6)) {
3
    if (number % 2 != 0) {
4
      oddNumbers.add(number);
5
  }
6
}



This is the functional way of filtering odd numbers:

Java
 




xxxxxxxxxx
1


 
1
List<Integer> oddNumbers = Stream.of(1, 2, 3, 4, 5, 6)
2
  .filter(number -> number % 2 != 0)
3
  .collect(Collectors.toList());



3. Beware of the NullPointerException

When writing new methods, try to avoid returning nulls if possible. It could lead to null pointer exceptions. In the snippet below, the highest method returns a null if the list has no integers.

Java
 




xxxxxxxxxx
1
15


 
1
class Items {
2
    private final List<Integer> items;
3
    public Items(List<Integer> items) {
4
            this.items = items;
5
    }
6
    public Integer highest() {
7
      if (items.isEmpty()) return null;
8
      Integer highest = null;
9
      for (Integer item : items) {
10
          if (items.indexOf(item) == 0) highest = item;
11
          else highest = highest > item ? highest : item;
12
      }
13
      return highest;
14
    }
15
}



Before directly calling a method on an object I recommend checking for nulls as shown below.

Java
 




xxxxxxxxxx
1


 
1
Items items = new Items(Collections.emptyList());
2
Integer item = items.highest();
3
boolean isEven = item % 2 == 0; // throws NullPointerException 
4
boolean isEven = item != null && item % 2 == 0  //



It can be pretty cumbersome to have null checks everywhere in your code though. If you are using Java 8+, consider using the Optional class to represent values that may not have valid states. It allows you to easily define alternate behavior and is useful for chaining methods.

In the snippet below, we are using Java Stream API to find the highest number with a method which returns an Optional. Note that we are using Stream.reduce, which returns an Optional value.

Java
 




xxxxxxxxxx
1
10


 
1
public Optional<Integer> highest() {
2
    return items
3
            .stream()
4
            .reduce((integer, integer2) -> 
5
                            integer > integer2 ? integer : integer2);
6
}
7
Items items = new Items(Collections.emptyList());
8
items.highest().ifPresent(integer -> {             //
9
    boolean isEven = integer % 2 == 0;
10
});



Alternatively, you could also use annotations such as @Nullable or @NonNull which will result in warnings if there is a null conflict while building the code. For instance, passing a @Nullable argument to a method that accepts @NonNull parameters.

4. Directly Assigning References From Client Code to a Field

References exposed to the client code can be manipulated even if the field is final. Let’s understand this better with an example.

Java
 




xxxxxxxxxx
1


 
1
private final List<Integer> items;
2
public Items(List<Integer> items) {
3
        this.items = items;
4
}



In the above snippet, we directly assign a reference from the client code to a field. The client can easily mutate the contents of the list and manipulate our code as shown below.

Java
 




xxxxxxxxxx
1


 
1
List<Integer> numbers = new ArrayList<>();
2
Items items = new Items(numbers);
3
numbers.add(1); // This will change how items behaves as well



Instead, consider cloning the reference or creating a new reference and then assigning it to the field as shown below:

Java
 




xxxxxxxxxx
1


 
1
private final List<Integer> items;
2
public Items(List<Integer> items) {
3
    this.items = new ArrayList<>(items);
4
}



The same rule applies while returning references. You need to be cautious so as not to expose internal mutable state.

5. Handle Exceptions With Care

While catching exceptions, if you have multiple catch blocks, ensure that the sequence of catch blocks is most specific to least. In the snippet below, the exception will never be caught in the second block since the Exception class is the mother of all exceptions.

Java
 




xxxxxxxxxx
1


 
1
try {
2
    stack.pop();
3
} catch (Exception exception) {
4
    // handle exception
5
} catch (StackEmptyException exception) {
6
    // handle exception
7
}



If the situation is recoverable and can be handled by the client (the consumer of your library or code) then it is good to use checked exceptions. e. g. IOException is a checked exception that forces the client to handle the scenario and in case the client chooses to re-throw the exception then it should be a conscious call to disregard the exception.

6. Ponder Over the Choice of Data Structures

Java collections provide ArrayList, LinkedList, Vector, Stack, HashSet, HashMap, Hashtable. It’s important to understand the pros and cons of each to use them in the correct context. A few hints to help you make the right choice:

  • Map: Useful if you have unordered items in the form of key, value pairs and require efficient retrieval, insertion, and deletion operations. HashMap, Hashtable, LinkedHashMap are all implementations of the Map interface.

  • List: Very commonly used to create an ordered list of items. This list may contain duplicates. ArrayList is an implementation of the List interface. A list can be made thread-safe using Collections.synchronizedList thus removing the need for using Vector. Here’s some more info on why Vector is essentially obsolete.

  • Set: Similar to list but does not allow duplicates. HashSet implements the Set interface.

7. Think Twice Before You Expose

There are quite a few access modifiers to choose from in Java — public, protected, private. Unless you want to expose a method to the client code, you might want to keep everything private by default. Once you expose an API, there’s no going back.

For instance, you have a class Library that has the following method to checkout a book by name:

Java
 




xxxxxxxxxx
1


 
1
public checkout(String bookName) {
2
    Book book = searchByTitle(availableBooks, bookName);
3
  availableBooks.remove(book);
4
  checkedOutBooks.add(book);
5
}
6

          
7
private searchByTitle(List<Book> availableBooks, String bookName) {
8
...
9
}



If you do not keep the searchByTitle method private by default and it ends up being exposed, other classes could start using it and building logic on top of it that you may have wanted to be part of the Library class. It could break the encapsulation of the Library class or it may be impossible to revert/modify later without breaking someone else’s code. Expose consciously!

8. Code to Interfaces

If you have concrete implementations of certain interfaces (e. g. ArrayList or LinkedList) and if you use them directly in your code, then it can lead to high coupling. Sticking with the List interface enables you to switch over the implementation any time in the future without breaking any code.

Java
 




xxxxxxxxxx
1


 
1
public Bill(Printer printer) {
2
    this.printer = printer;
3
}
4

          
5
new Bill(new ConsolePrinter());
6
new Bill(new HTMLPrinter());



In the above snippet, using the Printer interface allows the developer to move to another concrete class HTMLPrinter.

9. Don’t Force Fit Interfaces

Take a look at the following interface:

Java
 




xxxxxxxxxx
1


 
1
interface BookService {
2
        List<Book> fetchBooks();
3
    void saveBooks(List<Book> books);
4
    void order(OrderDetails orderDetails) throws BookNotFoundException, BookUnavailableException;    
5
}
6

          
7
class BookServiceImpl implements BookService {
8
...



Is there a benefit of creating such an interface? Is there a scope for this interface being implemented by another class? Is this interface generic enough to be implemented by another class? If the answer to all these questions is no, then I’d definitely recommend avoiding this unnecessary interface that you’ll have to maintain in the future. Martin Fowler explains this really well in his blog.

Well then, what’s a good use case for an interface? Let’s say we have a class Rectangle and a class Circle that has behavior to calculate perimeter. If there is a requirement, to sum up, the perimeter of all shapes — a use case for polymorphism, then having the interface would make more sense, as shown below.

Java
 




xxxxxxxxxx
1
26


 
1
interface Shape {
2
        Double perimeter();
3
}
4

          
5
class Rectangle implements Shape {
6
//data members and constructors
7
    @Override
8
    public Double perimeter() {
9
        return 2 * (this.length + this.breadth);
10
    }
11
}
12

          
13
class Circle implements Shape {
14
//data members and constructors
15
    @Override
16
    public Double perimeter() {
17
        return 2 * Math.PI * (this.radius);
18
    }
19
}
20

          
21
public double totalPerimeter(List<Shape> shapes) {
22
    return shapes.stream()
23
               .map(Shape::perimeter)
24
               .reduce((a, b) -> Double.sum(a, b))
25
               .orElseGet(() -> (double) 0);
26
} 



10. Override hashCode When Overriding Equals

Objects that are equal because of their values are called value objects. e. g. money, time. Such classes must override the equals method to return true if the values are the same. The equals method is usually used by other libraries for comparison and equality checks; hence overriding equals is necessary. Each Java object also has a hash code value that differentiates it from another object.

Java
 




xxxxxxxxxx
1
15


 
1
class Coin {
2
    private final int value;
3

          
4
    Coin(int value) {
5
        this.value = value;
6
    }
7

          
8
    @Override
9
    public boolean equals(Object o) {
10
        if (this == o) return true;
11
        if (o == null || getClass() != o.getClass()) return false;
12
        Coin coin = (Coin) o;
13
        return value == coin.value;
14
    }
15
}



In the above example, we have overridden only the equals method of Object.

Java
 




x


 
1
HashMap<Coin, Integer> coinCount = new HashMap<Coin, Integer>() {{
2
  put(new Coin(1), 5);
3
  put(new Coin(5), 2);
4
}};
5

          
6
//update count for 1 rupee coin
7
coinCount.put(new Coin(1), 7);
8

          
9
coinCount.size(); // 3 why? 



We would expect coinCount to update the number of 1 rupee coins to 7 since we override equals. But HashMap internally checks if the hash code for 2 objects is equal and only then proceeds to test equality via the equals method. Two different objects may or may not have the same hash code but two equal objects must always have the same hash code, as defined by the contract of the hashCode method. So checking for hash code first is an early exit condition. This implies that both equals and hashCode methods must be overridden to express equality.

Java (programming language) guidelines

Published at DZone with permission of Meenakshi Dhanani. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling
  • Java’s Next Act: Native Speed for a Cloud-Native World

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!