Correctly recognise an int[] passed as a varargs parameter

我们两清 提交于 2019-12-13 15:43:08

问题


There seems to be an edge case between the autoboxing and the varargs system for primitive arrays. Is there a clean (i.e. non-reflection) way around this?

Example:

public class Test {
  class A<T> {
    public void a(T... ts) {
      System.out.println(ts.getClass().getCanonicalName() + " of " + ts[0].getClass().getCanonicalName() + " = " + Arrays.toString(ts));
    }

  }

  public void test() {
    // These all work fine - presumably all parameters are autoboxed.
    new A().a(1, 2, 3);
    new A().a(1.0, 2.0, 3.0);
    new A().a(1L, 2L, 3L);
    // You can even mix them up - which is unexpected.
    new A().a(1.0f, 2.0d, 3L);
    // Works fine - even though I get a "non-varargs call to varargs method with inexact argument type ..." hint.
    new A().a(new Integer[]{1, 1});
    // No hint - and doesn't do as intended.
    new A().a(new int[]{1, 1});
    // Works fine.
    new A<Integer>().a(new Integer[]{1, 1});
  }

  public static void main(String args[]) {
    try {
      new Test().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }

}

Prints

java.lang.Object[] of java.lang.Integer = [1, 2, 3]
java.lang.Object[] of java.lang.Double = [1.0, 2.0, 3.0]
java.lang.Object[] of java.lang.Long = [1, 2, 3]
java.lang.Object[] of java.lang.Float = [1.0, 2.0, 3]
java.lang.Integer[] of java.lang.Integer = [1, 1]
java.lang.Object[] of int[] = [[I@4d815146]
java.lang.Integer[] of java.lang.Integer = [1, 1]

Notice that the int[] does not print correctly - it seems to be being boxed into an Object[] whose first value is my int[]. Everything else seems to box nicely.

I want the int[] call to print correctly without breaking the others.

P.S. If you can do it by reflection, by all means post. I'd just prefer not to use it.


回答1:


Unfortunately you need to add

String text;
if (ts[0] instanceof int[])
    text = Arrays.toString((int[]) ts[0]);
else
    text = Arrays.toString(ts);

I suspect there is an Apache common library to do this for you, but I don't know which one.




回答2:


It prints perfectly, and there is no boxing going on. The object you are passing in is an Array of int, which Java writes as default writes as [I@.... Java won't look inside the Array and try to autobox the ints for you.

To get prettier looking printouts, you can take a look at @PeterLawrey:s answer.




回答3:


I had a hack around with Peter's idea and this now seems to work fine. It looks rather horrid but I think if I hide it away somewhere and add loads of comments about why it is necessary I may get away with it.

If anyone can find a way of not duplicating the T[] boxed = makeTArray(it.length); by doing it in the caller I would appreciate it.

public class Test {
  class A<T> {
    public void a(T... ts) {
      System.out.println(
       ts.getClass().getCanonicalName() 
       + " of " 
       + ts[0].getClass().getCanonicalName() 
       + " = " 
       + Arrays.toString(Rebox.rebox(ts)));
    }
  }

  public void test() {
    // These all work fine - presumably all parameters are autoboxed.
    new A().a(1, 2, 3);
    new A().a(1.0, 2.0, 3.0);
    new A().a(1L, 2L, 3L);
    // You can even mix them up - which is unexpected.
    new A().a(1.0f, 2.0d, 3L);
    // Works fine - even though I get a "non-varargs call to varargs method with inexact argument type ..." hint.
    new A().a(new Integer[]{1, 1});
    // No hint - and doesn't do as intended - Does now!!
    new A().a(new int[]{1, 1});
    new A().a(new long[]{1, 1});
    new A().a(new float[]{1, 1});
    new A().a(new double[]{1, 1});
    // Works fine.
    new A<Integer>().a(new Integer[]{1, 1});
  }

  public static void main(String args[]) {
    try {
      new Test().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }

}

And the Rebox class.

/**
 * Can rebox a boxed primitive array into its Object form.
 *
 * Generally, if a primitive array is passed to a varargs it
 * is wrapped up as the first and only component of an Object[].
 * 
 * E.g. 
 * 
 * public void f(T... t) {};
 * f(new int[]{1,2});
 * 
 * actually ends up calling f with t an Object[1] and t[0] the int[].
 *
 * This unwraps it and returns the correct reboxed version.
 * 
 * In the above example it will return an Integer[].
 * 
 * Any other array types will be returned unchanged.
 *
 * @author OldCurmudgeon
 */
public class Rebox {
  public static <T> T[] rebox(T[] it) {
    // Default to return it unchanged.
    T[] result = it;
    // Special case length 1 and it[0] is primitive array.
    if (it.length == 1 && it[0].getClass().isArray()) {
      // Which primitive array is it?
      if (it[0] instanceof int[]) {
        result = rebox((int[]) it[0]);
      } else if (it[0] instanceof long[]) {
        result = rebox((long[]) it[0]);
      } else if (it[0] instanceof float[]) {
        result = rebox((float[]) it[0]);
      } else if (it[0] instanceof double[]) {
        result = rebox((double[]) it[0]);
      } else if (it[0] instanceof char[]) {
        result = rebox((char[]) it[0]);
      } else if (it[0] instanceof byte[]) {
        result = rebox((byte[]) it[0]);
      } else if (it[0] instanceof short[]) {
        result = rebox((short[]) it[0]);
      } else if (it[0] instanceof boolean[]) {
        result = rebox((boolean[]) it[0]);
      }
    }
    return result;
  }

  // Rebox each one separately.
  private static <T> T[] rebox(int[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Integer.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(long[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Long.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(float[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Float.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(double[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Double.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(char[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Character.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(byte[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Byte.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(short[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Short.valueOf(it[i]);
    }
    return boxed;
  }

  private static <T> T[] rebox(boolean[] it) {
    T[] boxed = makeTArray(it.length);
    for (int i = 0; i < it.length; i++) {
      boxed[i] = (T) Boolean.valueOf(it[i]);
    }
    return boxed;
  }

  // Trick to make a T[] of any length.
  // Do not pass any parameter for `dummy`.
  // public because this is potentially re-useable.
  public static <T> T[] makeTArray(int length, T... dummy) {
    return Arrays.copyOf(dummy, length);
  }

}


来源:https://stackoverflow.com/questions/16854959/correctly-recognise-an-int-passed-as-a-varargs-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!