开发者

How to avoid repetition when working with primitive types?

开发者 https://www.devze.com 2022-12-23 06:35 出处:网络
I have the need to perform algorithms on various primitive types; the algorithm is essentially the same with the exception of which type the variables are.So for instance,

I have the need to perform algorithms on various primitive types; the algorithm is essentially the same with the exception of which type the variables are. So for instance,

/**
* Determine if <code>value</code> is the bitwise OR of elements of <code>validValues</code> array. 
* For instance, our valid choices are 0001, 0010, and 1000.
* We are given a value of 1001.  This is valid because it can be made from
* ORing together 0001 and 1000.
* On the other hand, if we are given a value of 1111, this is invalid because
* you cannot turn on the second bit from left by ORing together those 3
* valid values.
*/
public static boolean isValid(long value, long[] validValues) {
    for (long validOption : validValues) {
        value &= ~validOption;
    }
    retur开发者_开发知识库n value == 0;
}

public static boolean isValid(int value, int[] validValues) {
    for (int validOption : validValues) {
        value &= ~validOption;
    }
    return value == 0;
}

How can I avoid this repetition? I know there's no way to genericize primitive arrays, so my hands seem tied. I have instances of primitive arrays and not boxed arrays of say Number objects, so I do not want to go that route either.

I know there are a lot of questions about primitives with respect to arrays, autoboxing, etc., but I haven't seen it formulated in quite this way, and I haven't seen a decisive answer on how to interact with these arrays.

I suppose I could do something like:

public static<E extends Number> boolean isValid(E value, List<E> numbers) {
    long theValue = value.longValue();
    for (Number validOption : numbers) {
        theValue &= ~validOption.longValue();
    }
    return theValue == 0;
}

and then

public static boolean isValid(long value, long[] validValues) {
    return isValid(value, Arrays.asList(ArrayUtils.toObject(validValues)));
}

public static boolean isValid(int value, int[] validValues) {
    return isValid(value, Arrays.asList(ArrayUtils.toObject(validValues)));
}

Is that really much better though? That way will create a lot more objects than the original implementation, though it DRYs up the source code. Any thoughts in this matter would be appreciated.


I asked a similar question before (Managing highly repetitive code and documentation in Java), and noted that the source code for java.util.Arrays is highly repetitive in its algorithms to deal with primitive array types.

In fact, the source code contains this comment:

The code for each of the seven primitive types is largely identical. C'est la vie.

The answer that I accepted suggests the use of a code generator that lets you work with code templates instead. There's also a comment that Sun/Oracle uses a templating system internally as well.

You can also use reflection to reduce repetition, but this is likely to be slow, and perhaps not worth the effort. If you want to test out its performance, this snippet demonstrates the technique:

import java.lang.reflect.Array;

static long maskFor(Class<?> c) {
    return (
        c.equals(int.class) ? 1L << Integer.SIZE :
        c.equals(short.class) ? 1L << Short.SIZE :
        c.equals(byte.class) ? 1L << Byte.SIZE :
        0
    ) - 1;
}   
public static void reflectPrimitiveNumericArray(Object arr) throws Exception {
    int length = Array.getLength(arr);
    Class<?> componentType = arr.getClass().getComponentType();
    long mask = maskFor(componentType);
    System.out.format("%s[%d] = { ", componentType, length);
    for (int i = 0; i < length; i++) {
        long el = Array.getLong(arr, i) & mask;
        System.out.print(Long.toBinaryString(el) + " ");
    }
    System.out.println("}");
}

You can pass an int[] for arr, as well as other primitive array types. Everything is cast into long, with bit-masking to address sign extension.

reflectPrimitiveNumericArray(new byte[] { (byte) 0xF0 });
// byte[1] = { 11110000 }
reflectPrimitiveNumericArray(new int[] { 0xF0F0F0F0 });
// int[1] = { 11110000111100001111000011110000 }
reflectPrimitiveNumericArray(new long[] { 0xF0F0F0F0F0F0F0F0L });
// long[1] = { 1111000011110000111100001111000011110000111100001111000011110000 }


If you look in java.util.Arrays, you'll see that even they had to specialize all their algorithms (binarySearch, equals, etc.) for each primitive type.

I would not recommend relying on autoboxing if performance is an issue, but if not (after you've profiled), it would be a valid option.


I think you're out of luck I'm afraid - without moving away from primitive types.


In a previous life, we had some primitive typed collections that were optimized for financial data (millions of orders in memory, using chunking arrays and such). Our solution was much like Trove's, with some stub files. The 'original' source file would be say... HashSet_key for example. With some stub classes for key and value, we followed Trove's model of using ant tasks to generate a HashSetInt, HashSetLong, etc...

I always thought this was a 'janky' method, however it worked. I am curious is anyone has ever tried Velocity, or possibly FMPP, and had some slightly cleaner results? The main problem I had with the ant solution is that all the code lives pretty close together, whereas in a lot of source code generation you may be able to separate template files better.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号