Everyday Java

Java is widely used in companies all over the world and is something of an industry standard. New cool languages come and go, but Java, despite its drawbacks, has been around a very long time and will stay a while longer. It is kind of like that difficult old guy at work who not everybody likes but everybody respects because he’s been around forever.

One of those new cool languages that could actually compete with Java is Scala, and rightfully so, but that’s for another time.

In the meantime, I would like to discuss some Java tricks and concepts I learned recently that were not emphasized enough in my Java courses. This should (hopefully) be valuable for other junior java developers out there.

Data Validation

As I was programming mainly in Java during my last summer internship, I found myself doing null checks all the freaking time. This is definitely one of the drawbacks of programming in Java so here are some tricks I found to make data validation in Java slightly simpler and more elegant.

If you often need to check that your String isn’t null or empty, instead of doing this

if (s != null) {
  if (s.length() != 0) {
    ...
  }
}  

You could instead use the StringUtils library like so:

if (!StringUtils.isEmpty(s)) {
  ...
}  

The isEmpty method returns true if the string is null or an empty string, both of which are often invalid cases.
Similarly, the Collections library can be used to check if a collection (like a list or a set) is null or empty in a single method call:

if (Collections.isEmpty(myList)) {
  ...
}  

Note that it may not be worthwhile importing those libraries if you just need this in one or two places. If you need this in several classesit is definitely worth the extra import.

Finally, if you are like me and are paranoid about NullPointerExceptions, there is a property that can make your life a bit easier. Java evaluates conditions from left to right, so as soon as it knows the outcome of an expression it stops evaluating the rest of it. For instance, this is safe to do:

if (s == null || s.contains("abc")) ...

because if s is null the expression must evaluate to TRUE and s.contains("abc") will not be called, so a NPE cannot occur (assuming the rest of your logic is good). In addition, it is also good practice to read the docs of whatever you use to understand how it handles null/empty values.

Logic flow

You might have run into situations where you ended up nesting several if statements and ended up with a million curly braces everywhere. For school projects you probably wouldn’t care since all that matters is that your code works (at least at my university). In a more professional setting your code must be readable and maintainable so it is in your interest to mind not just the correctness, but the style of your logic flow.

For example, consider a simple method that tells us if a triangle is isosceles, equilateral or scalene given three side lengths.

private String getTriangleType(int a, int b, int c) {
  if (a>=0 && b>=0 && c>=0) {
    if (!(a+b>c || a+c>b || b+c>a)) {
      if (a==b && b==c) {
        return "equilateral";
      }

      if (a==b || b==c || a==c) {
        return "isoceles";
      }

      return "scalene";
    }
  }
  return "invalid triangle";
}

This piece of code should work as intended and most people will be able to understand it, but imagine this nesting approach used more complex methods and large classes. It will give people headaches! It will also give you headaches when you go back to it months later.

One way to improve this is to exit as soon as an invalid (or any conclusive) condition is met. For example, if any side is negative there is no point in going any further in the code because we know and so it makes sense to return right away. The same applies for the triangle inequality, which states that the sum of any two sides must be smaller than the third side. We then get this refactored code:

private String getTriangleType(int a, int b, int c) {
  if (a<0 && b<0 && c<0) {
    return "invalid triangle: all sides must be positive";
  }

  if (a+b>c || a+c>b || b+c>a) {
    return "invalid triangle: triangle inequality not met";
  }

  if (a==b && b==c) {
    return "equilateral";
  }

  if (a==b || b==c || a==c) {
    return "isoceles";
  }

  return "scalene";
}

To me, this is already much better. Keep in mind that there are many other ways to rewrite or to improve this code. For instance, it could make sense to put the conditions inside separate methods, especially if they might get reused. You might then have private Boolean allSidesPositive(int a, int b, int c) and private Boolean allSidesEqual(int a, int b, int c), and so on.

Additionally, we could return enums instead of strings, which is good practice whenever the same string is used more than once in an app, and helps avoid the situation where one version of it is modified and the other isn’t, thus leading to inconsistencies and potential bugs.

Strings

Since strings in Java are immutable (unlike in C/C++) we must be careful in how we use them. There are of course design justifications and advantages to using immutable strings, but I will focus on the bad things (just like your ex-wife, amirite?).

== vs .equals

One major point here is string comparison. NEVER compare strings using the == operator! Instead, do string1.equals(string2). “But I used it in the past and it worked!” you might say. Alas, it is an unfortunate reality and it does work most of the time, but you do NOT want to rely on it.

The reason for that is that the == operator checks if the references to the objects are equal, which happens to sometimes be true because Java tries to optimize by using a common String reference between different objects, a process also called String interning. However it is easy to come up with a situation where == will not work, such as "test" == "!test".substring(1), which will yield false despite technically comparing identical strings. See here for more on this.

Building new strings

Avoid concatenating strings inside loops over several lines of code. If you understand the consequences of immutable strings then this should be easy for you to understand. Either way, here are some example of what NOT to do:

String new = string1.concat(string2);
...
String new1 = new.concat(string3);

or

String numbers = "";
for (int c = 0; c<10; c++) {
  numbers = numbers + " c"
}

Why? Because everytime you do that the original string’s characters and the new string’s characters are copied one by one from their original objects. The longer the strings, the more time and space it will require.

Instead, there is something called StringBuilder, which only copies over the contents of the strings when you invoke its toString() method. This will be faster and take less memory. Here is again the second bad example, fixed:

StringBuilder sb = new StringBuilder();

for (int c = 0; c<10; c++) {
  sb.append(c);
}

String numbers = sb.toString();