Some reminders when working with Java…if that is what you want to call them.

Math.abs() can return a negative value.

jshell> Math.abs(Integer.MIN_VALUE)
$1 ==> -2147483648

jshell> Math.abs(Integer.MIN_VALUE + 1)
$2 ==> 2147483647

The highest value a 32 bit integer can contain is +2147483647. Attempting to represent +2147483648 in a 32 bit int will effectively roll over to -2147483648. When using signed integers, the two’s complement binary representations of +2147483648 and -2147483648 are identical. This is not a problem, however, as +2147483648 is considered out of range.

Use compareTo() over subtraction pattern.

public class Testing {
    public static void main(String[] args) {
        final BankAccount accountOne = new BankAccount(1_073_741_825);
        final BankAccount accountTwo = new BankAccount(-1_073_741_823);
        final int comparison = accountOne.compareTo(accountTwo);
        System.out.println(comparison); // -2147483648
    }

    private record BankAccount(int balance) {
        public int compareTo(BankAccount anotherAccount) {
            return this.balance - anotherAccount.balance;
        }
    }
}

Subtracting a negative number from a positive number is addition. The comparison value 2147483648 is too large for an int and rolls over to -2147483648.

public class Testing {
    public static void main(String[] args) {
        final BankAccount accountOne = new BankAccount(1_073_741_825);
        final BankAccount accountTwo = new BankAccount(-1_073_741_823);
        final int comparison = accountOne.compareTo(accountTwo);
        System.out.println(comparison); // 1
    }

    private record BankAccount(int balance) {
        public int compareTo(BankAccount anotherAccount) {
            return Integer.compare(this.balance, anotherAccount.balance);
        }
    }
}

Comparison value is 1.

Floating Point Numbers

It’s 2022 and you are using floating point numbers. You can add a positive number to 1 and get a value less than 1.

jshell> DoubleStream.of(1.0, 1e-16).sum();
$2 ==> 0.9999999999999999
jshell> DoubleStream.of(1.0, 1e-16).reduce(0, Double::sum);
$3 ==> 1.0

DoubleStream double sum();

Returns the sum of elements in this stream. Summation is a special case of a reduction. If floating-point summation were exact, this method would be equivalent to: return reduce(0, Double::sum); However, since floating-point summation is not exact, the above code is not necessarily equivalent to the summation computation done by this method. The value of a floating-point sum is a function both of the input values as well as the order of addition operations. The order of addition operations of this method is intentionally not defined to allow for implementation flexibility to improve the speed and accuracy of the computed result. In particular, this method may be implemented using compensated summation or other technique to reduce the error bound in the numerical sum compared to a simple summation of double values. Because of the unspecified order of operations and the possibility of using differing summation schemes, the output of this method may vary on the same input elements.

Floating point addition is not commutative and I assume this is based on the Kahan sum implementation.

Remember, decimal literals are of type double and need to be cast down to float.

final float error = 0.0; // compile error
final float error = 0.0F;

But an int literal is only of type long if it ends with an L.

final long sum = 0L;

It needs to be cast up to a long. Not the consistency we would expect.

Zero

Lets not forget about +0 and -0. The IEEE-754 standard has two zeros. When you divide by each one, you get a different result.

System.out.println(1.0 / 0.0); // Infinity
System.out.println(1.0 / -0.0); // -Infinity

But…

System.out.println(-0.0 == 0.0); // true

…even though -0.0 and 0.0 hava different bit patterns. Care must be taken when implementing the absolute value of a number.

Modular arithmetic

I’ve been burned by this a lot. I enjoy Project Euler, Rosalind and Advent of Code. When I think of modulus, I think in terms of remainder classes. For any modulus n, there exists n - 1 remainder classes denoted as the least residue system modulo n. The set of integers
{0, 1, 2, ... , n − 1} less than n from Euclidean division.

Given two integers a and b, with b ≠ 0, there exist unique integers q and r such that

a = bq + r
and

0 ≤ r < |b|

In Java, the % is the remainder operator and Math.floorMod() is the modulo operator which is consistent with the Euclidean definition where the remainder is always non-negative, 0 ≤ r, and is thus consistent with the Euclidean division algorithm. Each language may have a different definition of this operation.

System.out.println(-18 % 5);               // -3
System.out.println(Math.floorMod(-18, 5)); // 2


URL class

public static void main(String[] args) throws InterruptedException, MalformedURLException {
    final Set<URL> domains = new HashSet<>();
    domains.add(new URL("https://www.google.com/"));
    System.out.println(domains.contains(new URL("https://www.google.com/"))); // true
    // Wait 5 minutes
    Thread.sleep(300_000);
    System.out.println(domains.contains(new URL("https://www.google.com/"))); // false
}

Java resolves domain names in URLs into IP addresses before generating hash codes for them. If you wait 5 minutes, Google will load balance to a new IP address, and any new URLs you construct will have different hash codes. This is well documented but serves as a reminder.

Boxed Types

Just a reminder when using boxed types…

List interface
List-api

final List<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,3));
final int first = 1;
list.remove(first);
System.out.println(list);
[1, 3]
final List<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,3));
final Integer first = 1;
list.remove(first);
System.out.println(list);
[2, 3]

Collections only use boxed (object) types but it’s not common to use a boxed type otherwise unless you are expecting a null or Optional check. Just a reminder…

final int a = 1000;
final int b = 1000;
System.out.println(a == b); // true
final Integer c = 128;
final Integer d = 128;
System.out.println(c == d); // false
final Integer e = 127;
final Integer f = 127;
System.out.println(e == f); // true
final Integer g = 1000;
System.out.println(a == g); // true: g is unboxed to a primitive.

Boxing Conversion

If the value p being boxed is an integer literal of type int between -128 and 127 inclusive (§3.10.1), or the boolean literal true or false (§3.10.3), or a character literal between ‘\u0000’ and ‘\u007f’ inclusive (§3.10.4), then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

But -128 < 10 < 127.

final Integer m = 10;
final Integer n = 10;
System.out.println(m == n); // true
final Integer x = new Integer(10);
final Integer y = new Integer(10);
System.out.println(x == y); // false

Integer(int) can be fooled but it is deprecated and marked for removal.

Remember, use if (x.intValue() == y.intValue()) or if (x.equals(y)).

StringBuilder

Remember…

final StringBuilder stringBuilder = new StringBuilder('[');

does not add a [ to the beginning as intended. The char passed in has it’s int value 91 used as the initial capacity of the StringBuilder object.

String

You can say that format string vulnerabilities are not a thing in Java, but String.format("%2147483647%") is not your friend. It tries to create a String of length Integer.MAX_VALUE, that is 2GB. To make it succeed (assuming your heap size is big enough), pick a slightly smaller size.

Remember, String literals get interned.

final String s1 = "hello";
final String s2 = new String("hello").intern();
System.out.println(s1 == s2); // true
final String s1 = "hello";
final String s2 = new String("hello");
System.out.println(s1 == s2); // false

…but don’t confuse reference comparison with content comparison. Use s1.equals(s2).

Remember, as with any programing language or tool, just because you can doesn’t mean you should.