简体   繁体   中英

“Generic” solution for primitive array?

I have classes that for processing primitive array input: CharArrayExtractor for char[], ByteArrayExtractor for byte[], IntegerArrayExtractor for int[], ...

public void CharArrayExtractor {

    public List<Record> extract(char[] source) {
        List<Record> records = new ArrayList<Record>();
        int recordStartFlagPos = -1;
        int recordEndFlagPos = -1;
        for (int i = 0; i < source.length; i++) {
            if (source[i] == RECORD_START_FLAG) {
                recordStartFlagPos = i;
            } else if (source[i] == RECORD_END_FLAG) {
                recordEndFlagPos = i;
            }
            if (recordStartFlagPos != -1 && recordEndFlagPos != -1) {
                Record newRecord = makeRecord(source, recordStartFlagPos,
                        recordEndFlagPos);
                records.add(newRecord);
                recordStartFlagPos = -1;
                recordEngFlagPos = -1;
            }
        }
    }
}

public void ByteArrayExtractor {

    public List<Record> extract(byte[] source) {
        // filter and extract data from the array.
    }
}

public void IntegerArrayExtractor {

    public List<Record> extract(int[] source) {
        // filter and extract data from the array.
    }
}

The problem here is that the algorithm for extracting the data is the same, only the types of input are different. Everytime the algorithm changes, I have to change all of the extractor classes.

Is there a way to make extractor classes more "generics"?

Best regards.

EDIT : It seems that every suggestion so far is to use autoboxing to archive generic. But the number of elements of the array is often large, so I avoid using autoboxing.

I added more specific implementation of how the data is being extracted. Hope it will clarify something.

New Idea

Or a different approach is wrapping the primitive arrays and covering them with the methods you use for your algorithm.

public PrimitiveArrayWrapper {
    private byte[] byteArray = null;
    private int[] intArray = null;
    ...

    public PrimitiveArrayWrapper(byte[] byteArray) {
        this.byteArray = byteArray;
    }

    // other constructors

    public String extractFoo1(String pattern) {
        if(byteArray != null) {
          // do action on byteArray
        } else if(....) 
        ...
    }
}

public class AlgorithmExtractor {
    public List<Record> do(PrimitiveArrayWrapper wrapper) {
        String  s= wrapper.extractFoo1("abcd");
        ...
    }
}

This mainly depends if you have a lot of methods to call which you would have to cover. but at least you must not edit the algorithm more over the way how to access the primitive array. Furthermor you would also be able to use a different object inside the wrapper.

Old Idea

Either use generics or what i also think about is to have three methods which convert the primitive types into value types.

public void Extractor {
    public List<Record> extract(byte[] data) {
        InternalExtractor<Byte> ie = new InternalExtractor<Byte>();
        return ie.internalExtract(ArrayUtils.toObject(data));
    }

    public List<Record> extract(int[] data) {
        ...
    }
}

public void InternalExtractor<T> {
    private List<Record> internalExtract(T[] data) {
        // do the extraction
    }
}

ArrayUtils is a helper class from commons lang from Apache.

interface Source
    int length();
    int get(int index);

extract(final byte[] source)
    extract( new Source(){
        int length(){ return source.length; }
        int get(int i){ return source[i]; }
    } );

// common algorithm
extract(Source source)
    for(int i=0; i<source.lenth(); i++)
        int data = source.get(i);
        ...

I'm not sure how your filter will work as it will not know anything about the type the array contains.

Using reflection you can possibly do what you want but you will loose compile time type safety.

The java.lang.reflect.Array class provides functions for manipulating an array without knowing its type.

The Array.get() function will return the value at the requested index of the array and if it is a primitive wrap it in its corresponding Object type. The downside is you have to change your method signature to accept Object instead of specific array types which means the compiler can no longer check the input parameters for you.

Your code would become:

public class ArrayExtractor {

    public List<Record> extract(Object array) {
        // filter and extract data from the array.
        int length = Array.getLength(array);
        for (int index = 0; index < length; index++) {
            Object value = Array.get(array, index);

            // somehow filter using value here
        }
    }
}

Personally I would prefer having type safety over using reflection even if it is a little more verbose.

Depends on what you're trying to achieve. But maybe you can work with primitive wrappers instead? Then you could write generic Extractor<? extends Number> Extractor<? extends Number> (here Number is the abstract class extended by all primitive wrappers).

Instead of passing each type, pass the class of the type as the below:

    public List<Record> extract(Class srcClass) {
        if (int[].class.equals(srcClass) {

           // filter and extract data from the int[] array.
        }
        else if (..) // do same for other types
    }
public void Extractor<T> {

    public List<Record> extract(T[] source) {
        // filter and extract data from the array.
    }
}

http://download.oracle.com/javase/tutorial/extra/generics/methods.html

You could do something like this.

public class ArrayExtractor<T>
{
    public List<T> extract (T[] source)
    {
        // filter and extract data from the array.
    }
}

You would have a generic Extractor class and your implementation would be the same.

I think is is possible to do create a method like this:

public List<Record> extract(List<Number> source) {
    // filter and extract data from the array.
}

And use Arrays.asList(yourPrimaryArrayType) , to make it compatible.


After my tests and the comment of Sean Patrick Floyd , you will be able to do this by create once some helper methods, for converting primitive arrays to lists:

public static void main(String[] args)
{
    int[] i = {1,2,3};
    System.out.println(extract(asPrimitiveList(i)));
}

public static List<Object> extract(List<Number> source) {
    List<Object> l = new ArrayList<Object>();
    l.add(0);
    for (Number n : source)
    {
        // I know this line is rubbish :D
        l.set(0, ((Number) l.get(0)).doubleValue() + n.doubleValue());
    }
    return l;
}

private static List<Number> asPrimitiveList(int[] ia)
{
    List<Number> l = new ArrayList<Number>(ia.length);
    for (int i = 0; i < ia.length; ++i)
    {
        l.add(ia[i]);
    }
    return l;
}

private static List<Number> asPrimitiveList(byte[] ia)
{
    List<Number> l = new ArrayList<Number>(ia.length);
    for (int i = 0; i < ia.length; ++i)
    {
        l.add(ia[i]);
    }
    return l;
}

private static List<Number> asPrimitiveList(char[] ia)
{
    List<Number> l = new ArrayList<Number>(ia.length);
    for (int i = 0; i < ia.length; ++i)
    {
        l.add(ia[i]);
    }
    return l;
}

You cant use Javas generics because of primitive type source, your best bet is to try some java reflection api to analyze the incoming source and invoke the extractors on your own.

No, it is never possible.

For example take a look at documentation of ArrayUtils.copyOf(byte[] original, int newLength) . And there exists other methods for remaining primitives. This is kind of same behavior (literally) you wanted. If it was possible similar code should exists somewhere else.

Additionally we can discuss more about how generic works, but it would be another issue, i guess.

Yes, you should be able to use generics:

    interface Extractor<T, R> {
        public List<R> extract(T source);
    }

    class BaseExtractor<T> implements Extractor<T, Record>
    {
        public List<Record> extract(T source)
        {
            //do your thing
        }
    }

Here, you would have to assume that T is a primitive array, as you cannot use primitives in generic definitions.

Or else, you could use the wrapper Objects and do it this way:

    interface Extractor<T, R> {
        public List<R> extract(T[] source);
    }

    class BaseExtractor<T> implements Extractor<T, Record>
    {
        public List<Record> extract(T[] source)
        {
            //do your thing
        }
    }

In this case, your generic T can be Byte , Integer , etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM