在 Java 中将基元数组转换为容器数组

Posted

技术标签:

【中文标题】在 Java 中将基元数组转换为容器数组【英文标题】:Converting Array of Primitives to Array of Containers in Java 【发布时间】:2011-04-15 18:41:22 【问题描述】:

有没有一种优雅的方法可以将原始数组转换为相应容器对象的数组——例如,将byte[] 转换为Byte[]?还是我坚持循环并手动执行?

是的,for 循环并不难。有点丑。

【问题讨论】:

反之亦然:***.com/questions/564392/… 【参考方案1】:

有没有一种优雅的方法可以将原始数组转换为数组 对应的容器对象?

假设你有一个字节数组:

byte[] b = new byte[20];
... (fill b) ...

那么就可以使用Arrays.setAll(..)进行转换了:

Byte[] w = new Byte[b.length];
Arrays.setAll(w, i -> b[i]);

Arrays.parallelSetAll(...) 更快:

Arrays.parallelSetAll(w, i -> b[i]);

验证结果:

System.out.println(b.getClass().getCanonicalName());
System.out.println(Arrays.toString(b));
System.out.println(w.getClass().getCanonicalName());
System.out.println(Arrays.toString(w));

如果您需要所有类型的原始数组的通用包装器,这里是:

public static Object[] wrap(Object a) 
    if (a == null)
        return null;
    int length = Array.getLength(a);
    Object b = length > 0 ? a : Array.newInstance(a.getClass().getComponentType(), 1);
    Object[] result = (Object[])Array.newInstance(Array.get(b, 0).getClass(), length);
    Arrays.parallelSetAll(result, i -> Array.get(a, i));
    return result;

像这样使用它:

Byte[] w = (Byte[])wrap(b);

【讨论】:

还有一个扩展版本可以正确处理多维数组:***.com/a/62212122/9772691【参考方案2】:

Apache Commons

Apache Commons / Lang 有一个类ArrayUtils 定义了这些方法。

所有方法调用toObject(...) 从原始数组转换为包装数组 全部调用toPrimitive(...)转换 从包装对象数组到 原始数组

例子:

final int[]     original        = new int[]  1, 2, 3 ;
final Integer[] wrappers        = ArrayUtils.toObject(original);
final int[]     primitivesAgain = ArrayUtils.toPrimitive(wrappers);
assert Arrays.equals(original, primitivesAgain);

番石榴

但是我会说包装原语的数组不是很有用,所以你可能想看看Guava,它提供了所有数字类型的列表,由原始数组支持:

List<Integer> intList = Ints.asList(1,2,3,4,5);
List<Long> longList   = Longs.asList(1L,2L,3L,4L,5L);
// etc.

关于这些数组支持的集合的好想法是

    它们是实时视图(即更新数组会更改列表,反之亦然) 包装对象仅在需要时创建(例如,在迭代列表时)

见:Guava Explained / Primitives


Java 8

另一方面,使用 Java 8 lambdas / 流,您可以使这些转换非常简单,而无需使用外部库:

int[] primitiveInts = 1, 2, 3;
Integer[] wrappedInts = Arrays.stream(primitiveInts)
                              .boxed()
                              .toArray(Integer[]::new);
int[] unwrappedInts = Arrays.stream(wrappedInts)
                             .mapToInt(Integer::intValue)
                             .toArray();
assertArrayEquals(primitiveInts, unwrappedInts);

double[] primitiveDoubles = 1.1d, 2.2d, 3.3d;
Double[] wrappedDoubles = Arrays.stream(primitiveDoubles)
                                .boxed()
                                .toArray(Double[]::new);
double[] unwrappedDoubles = Arrays.stream(wrappedDoubles)
                                  .mapToDouble(Double::doubleValue)
                                  .toArray();

assertArrayEquals(primitiveDoubles, unwrappedDoubles, 0.0001d);

请注意,Java 8 版本适用于 intlongdouble,但不适用于 byte,因为 Arrays.stream() 仅对 int[]long[]、@987654336 有重载@ 或通用对象 T[]

【讨论】:

Arrays.stream(Byte []) 是否过载? Java 8 解决方案不适用于发布的问题。 Arrays.stream() 没有 byte[] 的重载 @Peri461 是的。我今年才添加了 Java 8 的东西作为事后的想法,其余的答案比 Java 8 早几年 我现在明白了,为否决投票道歉。当我允许时会修复。 @Peri461 没有问题。否决票是游戏的合法部分,无需道歉。【参考方案3】:

添加a good answer 后,这是一个糟糕的答案,只是为了见鬼。 Apache Commons ArrayUtils 类让我困扰的是,相同的方法有 8 个版本,只是针对不同的输入类型。我找到了一种将任何原始数组转换为其等效包装器的通用方法(因此将 8 个不同的版本减少为一个)。这是代码:

public final class ArraysUtils 

    private ArraysUtils()     

    @SuppressWarnings("unchecked")
    public static Object[] toWrapperArray(final Object primitiveArray) 
        Objects.requireNonNull(primitiveArray, "Null values are not supported");
        final Class<?> cls = primitiveArray.getClass();
        if (!cls.isArray() || !cls.getComponentType().isPrimitive()) 
            throw new IllegalArgumentException(
                    "Only primitive arrays are supported");
        
        final int length = Array.getLength(primitiveArray);
        if (length == 0) 
            throw new IllegalArgumentException(
                    "Only non-empty primitive arrays are supported");
        
        final Object first = Array.get(primitiveArray, 0);
        Object[] arr = (Object[]) Array.newInstance(first.getClass(), length);
        arr[0] = first;
        for (int i = 1; i < length; i++) 
            arr[i] = Array.get(primitiveArray, i);
        
        return arr;
    


如您所见,这种方法有很多问题:

没有编译时安全,方法参数可以是任何东西,只有方法本身会验证运行时参数,严格拒绝空值、空数组、非数组和非原始数组 需要反思 如果不在原始类和包装类之间保留某种查找表,就无法支持空数组。

无论如何,这里有一个适用于所有必要场景的测试套件,使用 JUnit 的 Parameterized runner:

@RunWith(Parameterized.class)
public class ArraysUtilsTest 
    @Parameterized.Parameters(name = "0")
    public static List<Object> parameters() 
        return Arrays.asList(
                success(new int[]1, 2, 3, new Integer[]1, 2, 3),
                success(new long[]1L, 2L, 3L, new Long[]1L, 2L, 3L),
                success(new byte[]1, 2, 3, new Byte[]1, 2, 3),
                success(new short[]1, 2, 3, new Short[]1, 2, 3),
                success(new char[]'a', 'b', 'c', new Character[]'a', 'b', 'c'),
                success(new double[]1.0, 2.0, 3.0, new Double[]1.0, 2.0, 3.0),
                success(new float[]1.0f, 2.0f, 3.0f, new Float[]1.0f, 2.0f, 3.0f),
                success(new boolean[]true, false, true, new Boolean[]true, false, true),
                failure(null, NullPointerException.class, "Null"),
                failure("foo", IllegalArgumentException.class, "Non-array"),
                failure(new String[]"foo", "bar", IllegalArgumentException.class, "Non-primitive array"),
                failure(new int[0], IllegalArgumentException.class, "Empty array")


            );
    

    private static Object[] success(Object primitiveArray, Object[] wrapperArray) 
        return new Object[]
                primitiveArray.getClass().getCanonicalName(),
                primitiveArray, null, wrapperArray;
    

    private static Object[] failure(Object input,
                                    Class<? extends RuntimeException> exceptionClass,
                                    String description) 
        return new Object[]description, input, exceptionClass, null;
    

    @Parameterized.Parameter(0)
    // only used to generate the test name
    public String scenarioName;

    @Parameterized.Parameter(1)
    public Object inputArray;

    @Parameterized.Parameter(2)
    public Class<? extends RuntimeException> expectedException;

    @Parameterized.Parameter(3)
    public Object[] expectedOutput;


    @Test
    public void runScenario() 
        try 
            Object[] wrapped = ArraysUtils.toWrapperArray(inputArray);
            if (expectedException != null) 
                fail(String.format("Expected %s to be thrown",
                                   expectedException.getSimpleName()));
            
            assertThat(wrapped, is(equalTo(expectedOutput)));
         catch (RuntimeException e) 
            if (expectedException == null) 
                fail(String.format("Expected no exception but got %swith message '%s'",
                                   e.getClass().getSimpleName(),
                                   e.getMessage()));
            
            if(!expectedException.isInstance(e))
                fail(String.format("Expected %s but got %s with message '%s'",
                                   expectedException.getSimpleName(),
                                   e.getClass().getSimpleName(),
                                   e.getMessage()));
            
        
    



【讨论】:

【参考方案4】:

这是一种不使用任何外部库的简短通用方法,它适用于所有原语:

import static java.lang.reflect.Array.*;
import java.util.Arrays;

public class DeepConverter 

  public static void main(String args[])         
    long L1[][][] = 1,2,3,4, 5,6, 7,8,9,10,11;
    L1 = new long[2][0][7];
    Long L2[][] = (Long[][])box(L1);
    System.out.println(Arrays.deepToString(L2));        
  

  public static Object box(Object src)         
    try 
        int length = src.getClass().isArray() ? getLength(src) : 0;        
        if(length == 0)
            return src;        
        Object dest = newInstance(typeCastTo(wrap(get(src, 0))), length);        
        for(int i = 0; i < length; i++)
            set(dest, i, wrap(get(src, i)));        
        return dest;

     catch(Exception e) 
        throw new ClassCastException("Object to wrap must be an array of primitives with no 0 dimensions");
    
  

  private static Class<?> typeCastTo(Object obj) 
    Class<?> type = obj.getClass();
    if(type.equals(boolean.class)) return Boolean.class;
    if(type.equals(byte.class)) return Byte.class;
    if(type.equals(char.class)) return Character.class;
    if(type.equals(double.class)) return Double.class;
    if(type.equals(float.class)) return Float.class;
    if(type.equals(int.class)) return Integer.class;
    if(type.equals(long.class)) return Long.class;
    if(type.equals(short.class)) return Short.class;
    if(type.equals(void.class)) return Void.class;        
    return type;
  

【讨论】:

【参考方案5】:

只是建议一个替代方案,使用Guava,您可以使用BytesInts 等基本类型实用程序之一来创建包装器类型的List

byte[] bytes = ...
List<Byte> byteList = Bytes.asList(bytes);

这些方法实际上不是循环遍历和转换每个byte,而是创建一个由给定数组支持的列表。如果你真的需要Byte[],这显然不能直接给你你需要的东西(当然你可以使用.toArray(new Byte[bytes.length]) 得到它)。不过,对于对象来说,集合比数组要好得多,如果可能的话,应该首选。

【讨论】:

我同意。集合规则,数组很烂,番石榴规则(+1)【参考方案6】:

你必须遍历你的数组。


@seanizer 回答后更新:

基本上toObject(byte[] array) 方法将为您执行循环:

public static Byte[] toObject(byte[] array) 
    if (array == null) 
        return null;
     else if (array.length == 0) 
        return EMPTY_BYTE_OBJECT_ARRAY;
    
    final Byte[] result = new Byte[array.length];
    for (int i = 0; i < array.length; i++) 
        result[i] = new Byte(array[i]);
    
    return result;

除非你真的会使用 commons lang lib,否则你应该简单地重用这个方法并避免无用的依赖(恕我直言)。

【讨论】:

我不认为依赖是没用的。 StringUtils.* 方法可以节省大量时间。 ArrayUtils 是一个奖励 :-) @seanizer 我完全同意,Commons lang 真的很有用,我只是说只依赖一个静态方法是不行的。 此时我们应该删除这个答案。 @seanizer 做对了。 @Erick 我强烈反对。对一个问题的不同观点很重要 包含 StringUtils.* 比编写代码便宜。另一个罐子是什么?我们的项目已经有很多了。

以上是关于在 Java 中将基元数组转换为容器数组的主要内容,如果未能解决你的问题,请参考以下文章

如何在Java中将浮点数组转换为双精度数组?

如何将四元组的大型数组转换为三角形基元?

在Java中将字符串转换为“字符”数组

如何在Java中将对象数组转换为字符串数组

在java中将字符串数组元素转换为字节数组[重复]

如何在java中将字符串数组转换为int数组[重复]