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:
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?
Don't Repeat Yourself (DRY): Using DRY or Do not Repeat Yourself principle, you make sure that you stay away from duplicate code as often as you can. Rather you replace the duplicate code with abstractions or use data normalization. To reduce duplicity in a function, one can use loops and trees.
Having to change the same code multiple times harms your cycle time. If you have to apply a change in multiple places, then implementing that change will take longer. If the duplication is pervasive enough, it'll lead to a decreased delivery speed.
Duplication is bad, but… It isn't a question of whether you'll remember: it's a question of when you'll forget.” Which makes perfect sense. It's time well spent when you try to make your code streamlined and readable. You'll end up with a cleaner, easier-to-maintain, and more extensible code base as a result.
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.
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); }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With