1

I am attempting to write a helper print method so I can print items out to the console with less code and in an easier fashion.

I am however, running into issues printing arrays that are passed into my method because I cannot loop over a regular generic variable (E), but I am not seeing a way to fix it without possibly overloading the method.

Why does looping not work in my instance when I check that the variable is an array of generic type? I feel like this should at least allow a cast to an array (which is what the commented code shows now), but that fails too, which makes me think a cast from E to E[] is not possible in Java.

Here is my code:

@SafeVarargs@SuppressWarnings("unchecked")//SAFE
public static <E> void print(E... toPrint){
    //USED TO ENHANCE SYSTEM.OUT.PRINTLN AND MAKE EASIER TO TYPE
    E[] itemArray;
    for(E item: toPrint){
        if(item.getClass().isArray()){//IS ARRAY
            //System.out.println(item.getClass());
            //itemArray = (E[]) item;//WILL NOT WORK, CANNOT CONVERT
            ////IF LOOP ITEM ERROR NOT ITERABLE, BUT IT IS ARRAY?
            //for(E innerItem: itemArray)
                //System.out.println(innerItem);
        }else{
            System.out.println(item);
        }
    }
}

Updated code based on @Sweeper's Suggestion:

@SafeVarargs//SAFE
public static <E> void print(E... toPrint){
    //USED TO ENHANCE SYSTEM.OUT.PRINTLN AND MAKE EASIER TO TYPE

    for(E item: toPrint){
        if(item.getClass().isArray()){//IF IS ARRAY
            printHelperForArrays(item);//MUST CHECK IF PRIMITIVE[] OR OBJECT[] 
        }else if (item instanceof List){//IF ITEM IS LIST
            String[] stringArray = item.toString()
                    .replace("[","")//REMOVE [
                    .replace("]","")//REMOVE ]
                    .split(", ");//TURN TO ARRAY
            for(String innerItem: stringArray)
                System.out.println(innerItem);//NOW PRINT EVERYTHING OUT
        }else{
            System.out.println(item);
        }
    }//END FOR
}

@SuppressWarnings("unchecked")//SAFE
public static <E> void printHelperForArrays(E item){
    //USED TO PRINT OUT ARRAY ELEMENTS
    if(item.getClass() == int[].class){//IS INT[]

        String[] stringArray = Arrays.toString((int[])item)
             .replace("[","")//REMOVE [
             .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING

    }else if(item.getClass() == double[].class){//IS DOUBLE[]

         String[] stringArray = Arrays.toString((double[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

            for(String innerItem: stringArray)
                System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else if(item.getClass() == float[].class){//IS FLOAT[]

        String[] stringArray = Arrays.toString((float[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else if(item.getClass() == char[].class){//IS CHAR[]

        String[] stringArray = Arrays.toString((char[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else if(item.getClass() == boolean[].class){//IS BOOLEAN[]

        String[] stringArray = Arrays.toString((boolean[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else if(item.getClass() == byte[].class){//IS BYTE[]

        String[] stringArray = Arrays.toString((byte[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else if(item.getClass() == short[].class){//IS SHORT[]

        String[] stringArray = Arrays.toString((short[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else if(item.getClass() == long[].class){//IS LONG[]

        String[] stringArray = Arrays.toString((long[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

        for(String innerItem: stringArray)
            System.out.println(innerItem);//NOW PRINT EVERYTHING OUT

    }else{//IS OBJECT[] LIKE INTEGER[], STRING[], CHARACTER[]... SO E[] WORKS

        String[] stringArray = Arrays.toString((E[])item)
            .replace("[","")//REMOVE [
            .replace("]","")//REMOVE ]
            .split(", ");//TURN TO ARRAY

            for(String innerItem: stringArray)
                System.out.println(innerItem);//NOW PRINT EVERYTHING OUT
    }

}

Passed Args:

int[] int1 = {4, 5, 6};
Integer[] Integer2 = {7, 8, 9};

double[] double3 = {10.01, 11.01, 12.01};
Double[] Double4 = {13.01, 14.01, 15.01};

char[] char5 = {'A', 'B', 'C'};
Character[] Character6 = {'D', 'E', 'F'};

float[] float7 = {1.999f, 2.999f};
Float[] Float8 = {3.999f, 4.999f};

LinkedList<String> listOfStrings = new LinkedList<>();
listOfStrings.add("List Item 1");
listOfStrings.add("List Item 2");

//CALLED AS
print(1, 2, 3, "Hello", int1, Integer2, double3, 
    Double4, char5, Character6, float7, Float8, listOfStrings);

Output

1
2
3
Hello
4
5
6
7
8
9
10.01
11.01
12.01
13.01
14.01
15.01
A
B
C
D
E
F
1.999
2.999
3.999
4.999
List Item 1
List Item 2

Now everything prints on its own line whether it is a primitive, an element in a Wrapper class, an array, a list, or whatever.

ViaTech
  • 2,143
  • 1
  • 16
  • 51
  • You cannot use the same parameter as of a type and as of that type's array type: if `E` is an array type, then it cannot be `E[]`... Right? – ernest_k May 07 '18 at 16:15
  • Yeah, that makes perfect sense, but the call **if(item.getClass().isArray())** only looks for items that are classified as arrays...For example in the code I posted I commented out the issues with arrays, but if you simply take out the conditional and write **System.out.println(item);**, you are given a memory reference so the actual array contents can never be printed. You can pass E[] to this method without issue too, which seems odd logically but with generics that is possible because it is a generic type – ViaTech May 07 '18 at 16:26
  • @ViaTech If you pass a primitive array, `E` will be inferred to be the array type. If you pass a `T[]` where `T` is a reference type, `E` will be inferred to be `T`. Primitives just can't be generic types. You have to overload the method. – Sweeper May 07 '18 at 16:29

4 Answers4

1

If you write public static <E> void print(E... toPrint){ it will requires all inputs are the same class (E). And call method like print(1, 2, 3, "Hello"); will not work

You should change your method to public static void print(Object... toPrint)

And you're freely to cast (Object[])item.

public static void print(Object... items) {
    for (Object item: items) {
        if (item.getClass().isArray()) {
            Object[] objs =primitiveArrayFrom(item);
            if (objs == null) {
                objs = (Object[]) item;
            }
            for (Object obj: objs) {
                System.out.println(obj);
            }

        } else {
            System.out.println(item);
        }
    }
}

private static Object[] primitiveArrayFrom(Object obj) {
    if (obj instanceof int[]) {
        int[] integers = (int[]) obj;
       return IntStream.of(integers).boxed().toArray();
    }
    // Test for other primitives here
    //finally
    return null;
}

Unfortunately, there's no way to directly convert object of primitive array to Object[] so you have to convert it by primitiveArrayFrom () function.

If you're sure that your input are of same type (that is you don't mix String with int for example, then you definitely should write overload version that accept primitive type). Otherwise, give above implementation a try.

Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
1

Your code doesn't work because you are trying to cast an array type E to an array of arrays - E[].

If the execution goes into the if statement, that mean E is an array type. So E[] is an array of arrays. E[] and E are unrelated types.

So you need to some how let the compiler know that you are sure that E is an array and it can be looped over. However, there is no way to do that in Java.

So yeah, you need to overload print. This technique is widely used in the standard library. Just look in the Arrays class, almost every method there has overloads for all kinds of arrays. Java arrays have many limitations that you have to live with.

By the way, do you know there is a handy method called Arrays.toString? It can simplify your array printing process:

System.out.println(Arrays.toString(arr));

Maybe you don't need your print after all.

Note:

Your code works, just not for primitive type arrays, so if you pass in a Integer[] it will print it out successfully.

Edit:

You could work around this problem by checking the type against each type of primitive array:

public static void print(Object... toPrint) {
    for (Object item : toPrint) {
        if (item.getClass() == int[].class) {
            System.out.println(Arrays.toString((int[])item));
        } else if (item.getClass() == double[].class) { // check for all primitive types
            System.out.println(Arrays.toString((double[])item));
        } else if (item instanceof Object[]){
            System.out.println(Arrays.toString((Object[])item));
        } else {
            System.out.println(item);
        }
    }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Well my print method does enhance the standard **System.out.println();** method because it is shorter and now allows as many 'single' generic arguments to be passed as I need (i.e. int and String), but if you are saying that converting E to E[] is impossible that is starting to seem right, but in my above instance how would you overload the method to allow arrays to be printed still using generics? I feel there is an issue here because my original signature is still called when an array is passed as a parameter, thus calling **public static void print(E[]... toPrint)** would not work – ViaTech May 07 '18 at 16:31
  • @ViaTech So you want to call it with arrays and other things mixed together as parameters? Then you should not use a generic type for that, as Mạnh Quyết Nguyễn mentioned, using a generic type would mean that all the arguments passed to it has to be compatible with one particular type. – Sweeper May 07 '18 at 16:34
  • Hmm...okay that makes sense, but still unfortunate because that means there would be no way to pass **print(1, "two", arrayOfThree)**, and still have every element (even in the array) print from 1 method call. – ViaTech May 07 '18 at 16:41
  • @ViaTech my answer should work with that. Does it not? What error does it produce? – Sweeper May 07 '18 at 16:42
  • Okay cool no errors with the code you provided based on our discussion. I used what you wrote to give me the idea I needed to make the generic version work the way I needed. I will mark your answer as correct, but check out the updated code I put in my question in a few min. – ViaTech May 07 '18 at 17:25
  • @ViaTech Are you sure a generic type is what you want? With that you can't do `print(1, "two", arrayOfThree)`. – Sweeper May 07 '18 at 17:33
  • Check the code I updated @Sweeper, I can pass an argument like this: **print(1, 2, 3, "Hello", arr1, arr2, arr3, arr4, arr5, arr6);** – ViaTech May 07 '18 at 17:44
  • You don't actually need separate cases for each type of array -- as a trick, you can wrap the array in another array of 1 element, and give it to `Arrays.deepToString()`, and then strip out the outermost brackets from the resulting string – newacct May 28 '18 at 04:14
1

Use Array Wrapper class of primitive type int which is Integer will solve your problem.

Integer[] arr = {4, 5, 6};
print(arr);

Instead of :

print(1, 2, 3, "Hello");
int[] arr = {4, 5, 6};
print(arr);

Now, what happens when you pass int[] Array and try to convert it E[] it actually tries to convert into Integer[]. So, basically tries to autoboxing the array but only autoboxing of primitive type is possible but we can't auto box primitive type array inke int[].

Amit Bera
  • 7,075
  • 1
  • 19
  • 42
  • Makes sense @Amit Bera, I will give a +1. I did notice while coming up with the update I posted in my question thread that my issues while converting to E were only applicable if I was using primitive types. I think (or am pretty sure) these issues occur because generics are not meant to work with primitives in Java. – ViaTech May 07 '18 at 19:59
1

You can use Array.get() to index an array without knowing its type:

for (int i = 0, len = Array.getLength(item); i < len; i++)
    System.out.println(Array.get(item, i));
shmosel
  • 49,289
  • 6
  • 73
  • 138