Does java escape analysis also work for arrays with only one element?

392 views Asked by At

Background:

When you extract methods out of long code pieces, you often run into the call-by-value problem with primitive variables. You cannot change those primitive parameters in the extracted method so that the caller sees the changes. You can avoid that by making the primitive variable an array with only one element. Then it is effectively used call-by-reference. However it is now an object on the heap. Is the escape analysis of Java clever enough to understand that and use the stack despite that?

Given following code and the case it could not be inlined:

public class EscapeAnalysisTest {
    public static void main(String[] args) {
        final Set<Integer> integers = new HashSet<>();
        integers.add(1);
        integers.add(9);
        integers.add(8);
        integers.add(4);
        // and so on ...

        final long[] product = new long[1];
        final long[] sum = new long[1];
        final long[] count = new long[1];
        final int[] max = new int[1];
        final int[] min = new int[1];

        product[0] = 1L;
        max[0] = Integer.MIN_VALUE;
        min[0] = Integer.MAX_VALUE; 

        for (Integer i : integers) {
            calcSomeValues(product, sum, count, max, min, i);
        }

        System.out.println("Product :" + product[0]);   
        System.out.println("Sum :" + sum[0]);   
        System.out.println("Count:" + count[0]);
        System.out.println("Max:" + max[0]);
        System.out.println("Min:" + min[0]);            
    }

    private static void calcSomeValues(final long[] product, final long[] sum, final long[] count, final int[] max,
            final int[] min, Integer i) {
        product[0] *= i;
        sum[0] += i;
        count[0]++;
        max[0] = Math.max(max[0], i);
        min[0] = Math.min(min[0], i);
    }
}
1

There are 1 answers

0
jpmc26 On

Here's a better way to do this:

public class Test {
    public static class Summary {
        private long product;
        private long sum;
        private long count;
        private int max;
        private int min;

        private Summary() {
            product = 1;
            sum = 0;
            count = 0;
            max = Integer.MIN_VALUE;
            min = Integer.MAX_VALUE; 
        }

        public long getProduct() { return product; }
        public long getSum() { return sum; }
        public int getCount() { return count; }
        public int getMax() { return max; }
        public int getMin() { return min; }

        public static Summary summarize(Collection<Integer> ints) {
            Summary s = new Summary();

            s.count = ints.size();
            for (Integer i : ints) {
                s.product *= i;
                s.sum += i;

                if (i > s.max) {
                    // You can use Math.max if you want
                    s.max = i;
                }
                if (i < s.min) {
                    // You can use Math.min if you want
                    s.min = i;
                }
            }

            return s;
        }
    }

    public static void main(String[] args) {
        final Set<Integer> integers = new HashSet<>();
        integers.add(1);
        integers.add(9);
        integers.add(8);
        integers.add(4);
        // and so on ...

        Summary s = Summary.summarize(integers);

        System.out.println("Product: " + s.getProduct());
        System.out.println("Sum: " + s.getSum());
        System.out.println("Count: " + s.getCount());
        System.out.println("Max: " + s.getMax());
        System.out.println("Min: " + s.getProduct());
    }
}

Using arrays the way you have is just weird. Don't do it. This will confuse other programmers and isn't how the language is intended to be used. It violates the Principle of Least Astonishment.

Instead, find a way to make the system work for you without going into weird territory. You have multiple values that are logically associated with each other, and they're all computed at the same time. When you have several values that are used together, it's a good time to think about using a class. By using a class and a single method that does all the updating, your code is clear and sensible. The class I've provided actually ends up being immutable (as far as external code is concerned) because the logic of computing the summary is all inside the summarize method, which has access to the private attributes, so it's very well encapsulated. (The names could probably be better, but I think this is good enough as an example.) If modifying the private state in summarize is undesirable, this can be easily adapted by giving Summary arguments with the values of its instance variables and simply passing the values into the constructor after computing them as local variables, which would turn Summary into a very simple result object.

Keeping all this logic very localized and preventing callers from modifying the result makes it very easy to reason about what's going on. Your example code with length one arrays violates both of those principles and makes it harder to understand, use, or maintain the code.

Alternately, if you can use the values immediately after calculating them, you can skip the class and just calculate them all in line. You could do this via a loop or calculate them separately using built in functionality.