20

此代码有效

int h;
byte r;
h=r;

但这些不是

int[] h;
byte[] r;
h=r;

或者说

int[] h =new byte[4];

我想知道为什么?

4

6 回答 6

24

byte有从to的隐式转换int,但不是from byte[]to int[]。这很有意义 - JIT 编译器知道要获得 an 中的值int[],它只需将索引乘以 4 并将其添加到数据的开头(当然,在验证之后,并假设没有额外的填充)。如果您可以分配对变量的byte[]引用,那将不起作用-表示方式不同。int[]

该语言本可以设计为允许这种转换,但使其创建一个包含所有字节副本的新语言,但就 Java 其余部分的设计而言,这将是非常令人惊讶的,其中赋值运算符 只是复制一个运算符右侧的值到左侧的变量。int[]

或者,我们可以对 VM 施加限制,即每个数组访问都必须查看相关数组对象的实际类型,并确定如何适当地获取元素……但这会很可怕(甚至比当前的引用类型数组协方差更糟糕)。

于 2013-07-28T13:31:51.773 回答
12

这就是设计。当您分配byte给更宽int时,没关系。但是当您声明 时new byte[4],这是内存的 ["continuous"] 部分,粗略地说,等于 4 * 8 位(或 4 个字节)。一个int是 32 位,所以,从技术上讲,所有byte数组的大小都等于一个的大小int。在 C 中,您可以直接访问内存,您可以做一些指针魔术并将byte指针转换为int指针。在 Java 中,你不能,那是安全的。

无论如何,你为什么想要那个?免责声明:下面的代码被认为是极不可能在任何地方看到的,除了一些性能敏感的库/应用程序中最关键的部分。ideone:http: //ideone.com/e14Omr

评论已经足够解释了,我希望。

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class Main {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        /* too lazy to run with VM args, use Reflection */
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        /* get array address */
        Unsafe unsafe = (Unsafe)f.get(null);
        byte four_bytes[] = {25, 25, 25, 25};
        Object trash[] = new Object[] { four_bytes };
        long base_offset_bytes = unsafe.arrayBaseOffset(Object[].class);
        long four_bytes_address = unsafe.getLong(trash, base_offset_bytes); // <- this is it
        long ints_addr = unsafe.allocateMemory(16); // allocate 4 * 4 bytes, i.e. 4 ints
        unsafe.copyMemory(four_bytes_address + base_offset_bytes, ints_addr, 4); // copy all four bytes
        for(int i = 0; i < 4; i++) {
            System.out.println(unsafe.getInt(ints_addr + i)); //run through entire allocated int[],
                                                              // get some intestines
        }
        System.out.println("*****************************");
        for(int i = 0; i < 16; i++) {
            System.out.println(unsafe.getByte(ints_addr + i)); //run through entire allocated int[],
                                                              // get some intestines
        }
    }
}
于 2013-07-28T13:33:42.497 回答
11

差异首先是由于原始类型和引用类型之间的行为差​​异。

如果您不熟悉它,原始类型具有“值语义”。这意味着当您执行a = b;whena和isb原始类型(byteshortintlongfloatdoublebooleanchar)时,将复制数字/布尔值。例如:

int a = 3;
int b = a; // int value of a is copied to b
a = 5;
System.out.println(b); // outputs: 3

但是数组是对象,对象具有“引用语义”。这意味着当您执行a = b;wherea并且b都声明为数组类型时,所引用的数组对象将变为shared。从某种意义上说,值仍然被复制,但这里的“值”只是指向位于内存中其他位置的对象的指针。例如:

int[] a = new int[] { 3 };
int[] b = a; // pointer value of a is copied to b, so a and b now point at the same array object
a[0] = 5;
System.out.println(b[0]); // outputs: 5
a = null; // note: 'a' now points at no array, although this has no effect on b
System.out.println(b[0]); // outputs: 5

因此可以这样做,int = byte因为将要复制数值(因为它们都是原始类型),而且因为任何可能的 byte 类型值都可以安全地存储在 int 中(这是一种“扩大”的原始转换)。

但是int[]andbyte[]都是对象类型,所以当你这样做时,int[] = byte[]你要求共享(而不是复制)对象(数组)。

现在你要问了,为什么 int 数组和 byte 数组不能共享它们的数组内存?如果他们这样做意味着什么?

Int 是字节大小的 4 倍,因此如果 int 和 byte 数组具有相同数量的元素,那么这会导致各种胡说八道。如果您尝试以内存高效的方式实现它,那么在访问 int 数组的元素以查看它们是否实际上是字节数组时将需要复杂(并且非常慢)的运行时逻辑。从字节数组内存中读取的 int 必须读取并扩大字节值,并且 int 存储必须要么丢失高 3 个字节,要么抛出异常,说明空间不足。或者,您可以通过填充所有字节数组以使每个元素浪费 3 个字节,以一种快速但浪费内存的方式来完成,以防万一有人想将字节数组用作 int 数组。

另一方面,也许您想为每个 int 打包 4 个字节(在这种情况下,共享数组不会有相同数量的元素,具体取决于您用来访问它的变量的类型)。不幸的是,这也会导致胡说八道。最大的问题是它不能跨 CPU 架构移植。在小端PC 上,b[0]它指的是 的低字节i[0],但在 ARM 设备上b[0]可能指向 的高字节i[0](并且它甚至可以在程序运行时改变,因为 ARM 具有可切换的字节序)。访问数组的长度属性的开销也会变得更加复杂,如果字节数组的长度不能被 4 整除,会发生什么?!

您可以在 C 中执行此操作,但这是因为 C 数组没有明确定义的长度属性,并且 C 不会尝试保护您免受其他问题的影响。C 不在乎您是否超出了数组边界或混淆了字节序。但是Java确实关心,所以在Java中共享数组内存是不可行的。(Java 没有联合。)

这就是为什么int[].classandbyte[].class都分别扩展了 class Object,但没有一个 class 扩展了另一个。您不能将对字节数组的引用存储在声明为指向 int 数组的变量中,就像您不能List在类型变量中存储对 a 的引用一样String;它们只是不兼容的类。

于 2013-07-28T15:52:00.623 回答
2

当你说

int[] arr = new byte[5];

你复制参考。右侧是对字节数组的引用。本质上,这看起来像:

|__|__|__|__|__|
0  1  2  3  4            offset of elements, in bytes
^
|
reference to byte array

左侧是对 int 数组的引用。然而,这看起来应该是这样的:

|________|________|________|________|________|
0        4        8        12       16
^
|
reference to int array

因此,简单地复制参考是不可能的。因为,要获得 arr[1],代码将查看起始地址 +4(而不是起始地址 +1)。

实现您想要的唯一方法是创建一个具有相同数量元素的 int[] 并在那里复制字节。

不自动执行此操作的理由是:

  • 将单个字节解释为 int 基本上是免费的,尤其是不必分配内存。
  • 复制字节数组是完全不同的。必须分配新的 int 数组,它至少是字节数组的 4 倍。复制过程本身可能需要一些时间。

结论:在 Java 中,你总是可以说“我想把这个特殊的字节当作一个 int 对待”。但是你不能说:“我想把一些包含字节的数据结构(如数组或类实例)当作包含整数。”

于 2013-07-28T16:13:17.953 回答
1

简单地说,类型 byte[] 不扩展 int[]

于 2013-07-28T13:33:25.330 回答
-1

你不能,因为它像大元素一样将存储在较小的元素中。整数不能存储在字节中。决定这些类型分配的是我们的内存设计

于 2013-07-28T18:55:21.170 回答