37

In one of my Java projects I am plagued by code repetition due to the way Java handles (not) primitives. After having to manually copy the same change to four different locations (int, long, float, double) again, for the third time, again and again I came really close (?) to snapping.

In various forms, this issue has been brought up now and then on StackOverflow:

The consensus seemed to converge to two possible alternatives:

  • Use some sort of code generator.
  • What can you do? C'est la vie!

Well, the second solution is what I am doing now and it is slowly becoming dangerous for my sanity, much like the well known torture technique.

Two years have passed since these questions were asked and Java 7 came along. I am, therefore, hopeful for an easier and/or more standard solution.

  • Does Java 7 have any changes that might ease the strain in such cases? I could not find anything in the condensed change summaries, but perhaps there is some obscure new feature somewhere?

  • While source code generation is an alternative, I'd prefer a solution supported using the standard JDK feature set. Sure, using cpp or another code generator would work, but it adds more dependencies and requires changes to the build system.

    The only code generation system of sorts that seems to be supported by the JDK is via the annotations mechanism. I envision a processor that would expand source code like this:

    @Primitives({ "int", "long", "float", "double" })
    @PrimitiveVariable
    int max(@PrimitiveVariable int a, @PrimitiveVariable int b) {
        return (a > b)?a:b;
    }
    

    The ideal output file would contain the four requested variations of this method, preferrably with associated Javadoc comments e.t.c. Is there somewhere an annotation processor to handle this case? If not, what would it take to build one?

  • Perhaps some other trick that has popped up recently?

EDIT:

An important note: I would not be using primitive types unless I had a reason. Even now there is a very real performance and memory impact by the use of boxed types in some applications.

EDIT 2:

Using max() as an example allows the use of the compareTo() method that is available in all numeric boxed types. This is a bit trickier:

int sum(int a, int b) {
    return a + b;
}

How could one go about supporting this method for all numeric boxed types without actually writing it six or seven times?

Community
  • 1
  • 1
thkala
  • 84,049
  • 23
  • 157
  • 201
  • 3
    tricky and elaborate question. thumbs up! – MarianP Mar 23 '12 at 23:06
  • Most code generation used nowadays are bytecode generation in ClassLoader at runtime. In your case, you need class files for your own code compilation... So I would look at a compile-time annotation processor – Yves Martin Mar 24 '12 at 12:32
  • This guy presents a pretty elaborate solution with an annotation processor and generating classes using Velocity. You wouldn't need to use Velocity and only need to write something simpler IMO http://deors.wordpress.com/2011/10/08/annotation-processors/ – MarianP Mar 28 '12 at 21:41

8 Answers8

18

I tend to use a "super type" like long or double if I still want a primitive. The performance is usually very close and it avoids creating lots of variations. BTW: registers in a 64-bit JVM will all be 64-bit anyway.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 2
    +1 An interesting point: unless it has to do with arrays, having code specific to `byte`, `char` or `short` is probably useless - according to JLS all such values have to be promoted to `int` for most operations, so the bytecode is identical to the code for `int`... – thkala Mar 20 '12 at 20:20
  • Won't you get unexpected results in overflow cases? All your algorithms would have to account for the min / max values of the original numeric type. – vladimir e. Mar 22 '12 at 14:24
  • 2
    Generally underflow or overflows are usually undesirable, but you can usually cast to the appropriate type at the end. – Peter Lawrey Mar 22 '12 at 20:01
  • I will accept this answer, since it is what I ended up doing in most cases. Unfortunately, no other option seems to be ready for production use at this time - at least not without using a different programming language... – thkala Jul 05 '12 at 18:03
15

Why are you hung up on primitives? The wrappers are extremely lightweight and auto-boxing and generics does the rest:

public static <T extends Number & Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

This all compiles and runs correctly:

public static void main(String[] args) {
    int i = max(1, 3);
    long l = max(6,7);
    float f = max(5f, 4f);
    double d = max(2d, 4d);
    byte b = max((byte)1, (byte)2);
    short s = max((short)1, (short)2);
}

Edited

OP has asked about a generic, auto-boxed solution for sum(), and will here it is.

public static <T extends Number> T sum(T... numbers) throws Exception {
    double total = 0;
    for (Number number : numbers) {
        total += number.doubleValue();
    }
    if (numbers[0] instanceof Float || numbers[0] instanceof Double) {
        return (T) numbers[0].getClass().getConstructor(String.class).newInstance(total + "");
    }
    return (T) numbers[0].getClass().getConstructor(String.class).newInstance((total + "").split("\\.")[0]);
}

It's a little lame, but not as lame as doing a large series of instanceof and delegating to a fully typed method. The instanceof is required because while all Numbers have a String constructor, Numbers other than Float and Double can only parse a whole number (no decimal point); although the total will be a whole number, we must remove the decimal point from the Double.toString() before sending it into the constructor for these other types.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 16
    Performance, performance, performance. The one time I used the boxed types my algorithms became 3-4 times slower - and then I ran out of memory... – thkala Mar 12 '12 at 11:32
  • Admittedly the JVM at the time was not using compressed pointers, but I think the performance would still be worse. And I already have quite a bit of existing code that would take a lot to refactor... – thkala Mar 12 '12 at 11:33
  • There is also another issue in your method: How would you perform arithmetic operations in the method body? – thkala Mar 12 '12 at 11:41
  • You should update your question if performance is a key criteria for a solution. While you say the boxing solution is 3-4 time slower, is this an actual bottleneck in the app? You're CPU-bound not I/O bound? – SteveD Mar 12 '12 at 11:54
  • @SteveD: it's a statistics application and it is indeed CPU bound. The speed measured was based the total execution time. I am trying to cobble up a new benchmark to test the Java 7 VM... – thkala Mar 12 '12 at 12:06
  • @thkala exactly what "arithmetic operations" did you have in mind? And let's run a benchmark while we're at it, so please make your examples "real world". My suspicion is that the JVM would in-line the method call using primitives at runtime if the method body allows it. – Bohemian Mar 12 '12 at 12:15
  • 3
    @Bohemian: With the Oracle server VM 1.7.0_02, your generic method takes 7 times longer to run than the equivalent int max(int, int) implementation and 12 times longer than long max(long, long). Even if the absolute difference is minimal, the relative difference may very well be relevant. And to the arithmetic operations: Is it even possible to do simple things like addition and subtraction with Number instances? – jarnbjo Mar 12 '12 at 12:57
  • 1
    @thkala You got out of memory with boxed types. Have you used Integer.valueOf for instance ? You want to reduce the global execution time, is your code multi-threaded ? – Yves Martin Mar 24 '12 at 02:43
  • @YvesMartin: there is no object cache for `Double` objects. And of course my code is multithreaded... not that throwing more processors at a performance reduction of the magnitude imposed by boxed types is a proper solution. – thkala Mar 24 '12 at 07:55
  • 1
    I awarded the first bounty to this answer because I have to acknowledge that I set myself up by choosing `max()` as an example. I'd really like to know if you have a solution for `sum()` in my updated question, even one that does not handle the performance issue... – thkala Mar 26 '12 at 17:23
5

Does Java 7 have any changes that might ease the strain in such cases?

No.

Is there somewhere an annotation processor to handle this case?

Not that I am aware of.

If not, what would it take to build one?

Time, or money. :-)

This seems to me like a problem-space where it would be difficult to come up with a general solution that works well ... beyond trivial cases. Conventional source code generation or a (textual) preprocessor seems more promising to me. (I'm not an Annotation processor expert though.)

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
4

If the extraordinary verbosity of Java is getting to you, look into some of the new, higher-level languages which run on the JVM and can interoperate with Java, like Clojure, JRuby, Scala, and so on. Your out-of-control primitive repetition will become a non-issue. But the benefits will go much further than that -- there are all kinds of ways which the languages just mentioned allow you to get more done with less detailed, repetitive, error-prone code (as compared to Java).

If performance is a problem, you can drop back into Java for the performance-critical bits (using primitive types). But you might be surprised at how often you can still get a good level of performance in the higher-level language.

I personally use both JRuby and Clojure; if you are coming from a Java/C/C#/C++ background, both have the potential to change the way you think about programming.

Alex D
  • 29,755
  • 7
  • 80
  • 126
  • Or groovy. Do not be forgetting groovy! – bharal Mar 19 '12 at 13:08
  • Ah, but the OP wants "performance, performance, performance" ... for these particular algorithms. – Stephen C Mar 21 '12 at 13:14
  • 1
    @StephenC, that's a good point. It's just that I have already seen a lot of questions on SO where people complain about how verbose Java is and ask for ways to make their code more concise, and for me the answer is obvious; rather than doing unnatural things with Java, just switch to a higher-level language! – Alex D Mar 21 '12 at 19:43
  • 1
    Upvoted: if the language feels clunky, you should probably use another one. As for performance, one can either optimize low-level, or write code that scales out easily. Scala would be a very good option, in that case. – cthulhu Apr 08 '12 at 08:54
3

Heh. Why not get sneaky? With reflection, you can pull the annotations for a method (annotations similar to the example you've posted). You can then use reflection to get the member names, and put in the appropriate types... In a system.out.println statement.

You would run this once, or each time you modded the class. The output could then be copy-pasted in. This would probably save you significant time, and not be too hard to develop.

Hm ,as for the contents of the methods... I mean, if all your methods are trivial, you could hard code the style (ie if methodName.equals("max") print return a>b:a:b etc. Where methodName is determined via reflection), or you could, ummmmm... Hm. I'm imagining the contents can be easily copy pasted, but that just seems more work.

Oh! Whty not make another annotation called " contents ", give it a string value of the method contents, add that to the member, and now you can print out the contents too.

In the very least, the time spent coding up this helper, even if about as long as doing the tedious work, well, it would be more interesting, riiiight?

bharal
  • 15,461
  • 36
  • 117
  • 195
  • 2
    It might be more interesting, but I'd prefer not to be the programmer who inherits the job of maintaining the code. (Or more accurately, I'd prefer not to be that programmer's manager ... assuming a programmer of "average" Java skills.) – Stephen C Mar 23 '12 at 11:52
  • Compile-time annotation processing can definitely be used to generate source files. I have seen a couple of tutorials, although none used the original source file as input, which is what I'd want to see. Otherwise, the code would move into a bunch of string literals that would be impossible to maintain... – thkala Mar 26 '12 at 17:12
  • @thkala - You don't need to code generate from String literals. You'll get much more maintainable code if you turn the source code into Velocity or Freemarker templates, and then run the templates with different parameters for different combinations of primitive types. – Stephen C Mar 30 '12 at 01:26
  • @StephenC Purely textual templating will confuse all the IDE type checking, cross referencing, and Javadoc creation tools. You need to start with Java (e.g. write the code for long) and have the processor generate the classes for int. – toolforger May 27 '20 at 18:49
1

Your question is pretty elaborate as you already seem to know all the 'good' answers. Since due to language design we are not allowed to use primitives as generic parameter types, the best practical answer is where @PeterLawrey is heading.

public class PrimitiveGenerics {

    public static double genericMax( double a, double b) {
        return (a > b) ?a:b;
    }


    public int max( int a, int b) {
        return (int) genericMax(a, b);
    }
    public long max( long a, long b) {
        return (long) genericMax(a, b);
    }
    public float max( float a, float b) {
        return (float) genericMax(a, b);
    }
    public double max( double a, double b) {
        return (double) genericMax(a, b);
    }


}

The list of primitive types is small and hopefully constant in future evolution of the language and double type is the widest/most general.

In the worst case, you compute using 64 bit variables where 32 bit would suffice. There is a performance penalty for conversion(tiny) and for pass by value into one more method (small), but no Objects are created as this is the main (and really huge) penalty for using primitive wrappers.

I also used a static method so it is bound early and not in run-time, although it is just one and which is something that JVM optimization usually takes care of but it won't hurt anyway. May depend on real case scenario.

Would be lovely if someone tested it, but I believe this is the best solution.

UPDATE: Based on @thkala's comment, double may only represent long-s until certain magnitude as it loses precision (becomes imprecise when dealing with long-s) after that:

public class Asdf2 {

    public static void main(String[] args) {
        System.out.println(Double.MAX_VALUE); //1.7976931348623157E308
        System.out.println( Long.MAX_VALUE); //9223372036854775807
        System.out.println((double) Long.MAX_VALUE); //9.223372036854776E18
    }
}
MarianP
  • 2,719
  • 18
  • 17
  • 3
    Something that should be pointed out: `double` may be the most generic primitive in terms of range, but not in terms of precision. A `long` value that uses more than 53 bits cannot be stored in a `double` without a loss in precision... – thkala Mar 26 '12 at 17:08
  • You are definitely right and I did not realize that. System.out.println(Double.MAX_VALUE); 1.7976931348623157E308 System.out.println(Long.MAX_VALUE); 9223372036854775807 System.out.println((double) Long.MAX_VALUE); 9.223372036854776E18 – MarianP Mar 26 '12 at 21:20
1

From the performance point of view (I make a lot of CPU-bound algorithms too), I use my own boxings that are not immutable. This allows using mutable numbers in sets like ArrayList and HashMap to work with high performance.

It takes one long preparation step to make all the primitive containers with their repetitive code, and then you just use them. As I also deal with 2-dimensional, 3-dimensional etc values, I also created those for myself. The choice is yours.

like:
Vector1i - 1 integer, replaces Integer
Vector2i - 2 integer, replaces Point and Dimension
Vector2d - 2 doubles, replaces Point2D.Double
Vector4i - 4 integers, could replace Rectangle
Vector2f - 2-dimensional float vector
Vector3f - 3-dimensional float vector
...etc...
All of them represent a generalized 'vector' in mathematics, hence the name for all these primitives.

One downside is that you cannot do a+b, you have make methods like a.add(b), and for a=a+b I chose to name the methods like a.addSelf(b). If this bothers you, take a look at Ceylon, which I discovered very recently. It's a layer on top of Java (JVM/Eclispe compatbile) created especially to address it's limitations (like operator overloading).

One other thing, watch out when using these classes as a key in a Map, as sorting/hashing/comparing will go haywire when the value changes.

Mark Jeronimus
  • 9,278
  • 3
  • 37
  • 50
0

I'd agree with previous answers/comments that say there isn't a way to do exactly what you want "using the standard JDK feature set." Thus, you are going to have to do some code generation, although it won't necessarily require changes to the build system. Since you ask:

... If not, what would it take to build one?

... For a simple case, not too much, I think. Suppose I put my primitive operations in a util class:

public class NumberUtils {

    // @PrimitiveMethodsStart
    /** Find maximum of int inputs */
    public static int max(int a, int b) {
        return (a > b) ? a : b;
    }

    /** Sum the int inputs */
    public static int sum(int a, int b) {
        return a + b;
    }
    // @PrimitiveMethodsEnd

    // @GeneratedPrimitiveMethodsStart - Do not edit below
    // @GeneratedPrimitiveMethodsEnd
}

Then I can write a simple processor in less than 30 lines as follows:

public class PrimitiveMethodProcessor {
    private static final String PRIMITIVE_METHODS_START = "@PrimitiveMethodsStart";
    private static final String PRIMITIVE_METHODS_END = "@PrimitiveMethodsEnd";
    private static final String GENERATED_PRIMITIVE_METHODS_START = "@GeneratedPrimitiveMethodsStart";
    private static final String GENERATED_PRIMITIVE_METHODS_END = "@GeneratedPrimitiveMethodsEnd";

    public static void main(String[] args) throws Exception {
        String fileName = args[0];
        BufferedReader inputStream = new BufferedReader(new FileReader(fileName));
        PrintWriter outputStream = null;
        StringBuilder outputContents = new StringBuilder();
        StringBuilder methodsToCopy = new StringBuilder();
        boolean inPrimitiveMethodsSection = false; 
        boolean inGeneratedPrimitiveMethodsSection = false; 
        try {
            for (String line;(line = inputStream.readLine()) != null;) {
                if(line.contains(PRIMITIVE_METHODS_END)) inPrimitiveMethodsSection = false;
                if(inPrimitiveMethodsSection)methodsToCopy.append(line).append('\n');
                if(line.contains(PRIMITIVE_METHODS_START)) inPrimitiveMethodsSection = true;
                if(line.contains(GENERATED_PRIMITIVE_METHODS_END)) inGeneratedPrimitiveMethodsSection = false;
                if(!inGeneratedPrimitiveMethodsSection)outputContents.append(line).append('\n');
                if(line.contains(GENERATED_PRIMITIVE_METHODS_START)) {
                    inGeneratedPrimitiveMethodsSection = true;
                    String methods = methodsToCopy.toString();
                    for (String primative : new String[]{"long", "float", "double"}) {
                        outputContents.append(methods.replaceAll("int\\s", primative + " ")).append('\n');
                    }
                }
            }
            outputStream = new PrintWriter(new FileWriter(fileName));
            outputStream.print(outputContents.toString());
        } finally {
            inputStream.close();
            if(outputStream!= null) outputStream.close();
        }
    }
}

This will fill the @GeneratedPrimitiveMethods section with long, float and double versions of the methods in the @PrimitiveMethods section.

    // @GeneratedPrimitiveMethodsStart - Do not edit below
    /** Find maximum of long inputs */
    public static long max(long a, long b) {
        return (a > b) ? a : b;
    }
    ...

This is an intentionally a simple example, and I'm sure it doesn't cover all cases, but you get the point and can see how it could be extended e.g. to search multiple files or use normal annotations and detect method ends.

Furthermore, whilst you could set this up as a step in your build system, I set this up to run as a builder before the Java builder in my eclipse project. Now whenever I edit the file and hit save; it's updated automatically, in place, in less than a quarter of a second. Thus, this becomes more of a editing tool, than a step in the build system.

Just a thought...

Ian Jones
  • 1,998
  • 1
  • 17
  • 17
  • 1
    Err... this is *not* an annotation processor - not a proper one anyway, since it cannot be used with `apt` or `javac` directly. If I were about to use something like this, I'd rather use `cpp` or any other existing tool than write my own... – thkala Apr 01 '12 at 23:18
  • Indeed - this is definitely not an annotation processor. You question in part asks: can I avoid manual primitive code duplication without additional dependencies or changing my build system? This was the quickest/simplest way I could think of achieving that. If you're willing to add dependencies at build time, I'd use an existing tool too. However, I have used simple custom code generators to automate various tedious tasks in the past with good success, so thought it might be worth mentioning. – Ian Jones Apr 02 '12 at 07:04