有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么int变量在分配给字节变量时抛出错误,而不是int文本?

我最近开始学习Java,不能仅仅理解该语言的一个特性

当我写下面的代码时,我没有得到任何错误(而且明智地说,我不应该这样做!):

byte b = 10 * 2

但是,当我键入以下代码时,编译器会抛出一个错误:

int i = 10;
byte b = i * 2

当编译器可以对10 * 2执行检查以确保它小于byte的范围时,为什么它不能同时对i * 2执行检查并查看它是否小于byte的范围

它是否与位的低级表示有关,或者与内存有关


共 (6) 个答案

  1. # 1 楼答案

    我对任何特定于Java的东西都没有把握,但任何现代编译器都会执行完全是常量的常量折叠到“折叠”表达式。也就是说,10*2倍于20倍,因此编译器将其视为键入byte b = 20;

    对于编译器来说,尝试并优化变量是不实际的。尽管在您提供的示例中,查看并知道i10相对简单,但如果编译器试图优化它并知道i是什么,那么它必须维护自己的符号表,并且本质上是一个解释器。由于java是一种预编译语言,这就违背了它的目的

    详细说明:

    编译器和解释器之间有区别。编译器接受源代码作为输入,并在后台编写机器代码。当机器代码运行时,将执行操作/执行/计算。Java是一种编译语言,所以它的编译器没有做太多计算,它只是编写可以在Java虚拟机上运行的机器代码。另一方面,Python是一种解释语言。运行python程序时,它不会尝试对i * 2进行任何类型转换,直到它实际计算i * 2

    现在,有时编译器试图变得更智能,并内置了“优化”这意味着,他们不用编写执行某些操作的机器代码,而是用更少的指令编写机器代码,因为它知道它将是什么(因此编译器会进行一些计算来实现这一点)。在您的示例中,编译器不需要编写存储数字10的机器指令,而是存储数字2,将它们相乘,然后存储结果,而是将10和2相乘,然后编写一条机器指令来存储结果

    当我们引入变量时,编译器就更难优化并弄清楚变量是什么。实际的编译程序(Java编译器)必须记住,i现在是一个包含数字10的变量。如果我们想要优化,只是为了知道我们可以将i*2分配给byte,这意味着编译器必须记住每一个整数变量,以防它在以后的表达式中被分配到一个字节——在这一点上,它真的不值得优化,因为编译器花费了额外的计算(额外的编译工作),而这并没有真正带来任何好处。符号表(如上所述)本质上是一个记住变量及其值的表

  2. # 2 楼答案

    虽然计算结果确实适合这个特定情况下的一个字节,这肯定不会超出编译器对这段代码的想象,但是i已经被声明为int,因此我怀疑编译器将因此应用任何可能int的规则,对于任何可能的int,分配都不起作用

  3. # 3 楼答案

    在java中,每当尝试执行算术表达式时,如果表达式包含任何类型的变量,java都会将该表达式的所有元素转换为该表达式中可用的最高数据类型

    所以

    当执行10*2时,两个操作数都是文本,不是变量,因此操作数和结果的数据类型也不会自动升级,除非结果超出数据类型的范围,这里是字节,而20完全在字节范围之下

    但当你做i*2时,表达式由变量组成,其中i是int,结果是20,但是它的类型是int。因为操作数被自动提升为int,也就是说,当表达式被计算时,20将被提升为int,结果将是int,因为两个操作数都是int。并且int不能存储在字节中,即使它在其范围内。因为编译器会认为,如果在一个字节中存储int,就会丢失值

    因此,在这种情况下,必须强制将其键入字节

    byte b = (byte)(i*2);
    

    试着运行这个你会惊讶的:

    byte b = 10;
    b = b * 2;
    

    对此的解释仍然与上述相同

  4. # 4 楼答案

    有些语言允许编译器自由地尽可能地智能化

    但是Java不是这样一种语言。Java的一个目标是,您可以使用许多不同的编译器编译代码,每次都得到相同的结果,这样您就不必担心IDE、本地命令行编译器和生产构建系统是否都以相同的方式处理代码

    那么编译器拒绝的原因是什么

    int i = 10;
    byte b = i * 2;
    

    只有当所有编译器都被要求接受它时,它才能接受它;这意味着该规范必须指定编译器在编译时计算i值的确切条件范围。这将是一个复杂的混乱局面,每个编译器都必须完全正确

    因此,相反,该规范以一种相当简单的方式定义常量表达式(请参见https://docs.oracle.com/javase/specs/jls/se12/html/jls-15.html#jls-15.28),并且仅当右侧是相关类型范围内的常量表达式时,才允许隐式缩小转换(请参见https://docs.oracle.com/javase/specs/jls/se12/html/jls-5.html#jls-5.2

    当然,您可以通过编写cast来解决这个问题,以便执行显式缩小转换:

    int i = 10;
    byte b = (byte)(i * 2);
    

    但是编译器不会为您检查20是否在byte范围内;你需要自己去做

    或者,可以将i设为常量:

    final int i = 10;
    byte b = i * 2;
    
  5. # 5 楼答案

    这里i是一个整数,i*2也是一个整数。将整数变量赋值为字节是非法的。可以通过显式地将其强制转换为byte来实现这一点

    byte b = (byte) (i * 2);
    

    Java不会执行任何值评估,以确定它是否适合/不适合目标类型

  6. # 6 楼答案

    重点是:

    int i = 10;
    byte b = i * 2
    

    真的很“清楚”,不是吗。人类知道b必须具有20的值,并且20非常适合字节范围

    但假设:

    int i = someMethodCall();
    byte b = i * 2
    

    现在,当看到someMethodCall()身体时,也许你可以得出相同的结论。但你也会同意:这本身就使得决定i*2是否仍在字节范围内变得更加复杂

    长话短说:编译器可以(而且确实)对源代码进行各种分析。例如,尽可能在编译时计算事物。但是到底发生了什么取决于A)语言规范和B)编译器实现

    问题是:编写一个编译器,简单地说:“使用int值初始化字节值是无效的”,这是非常简单的。要编写一个能够自行判断“这里它是有效的”的编译器,需要付出更多的努力。然后,会有很多情况,编译器不知道,然后回来抱怨

    换句话说:创建java的人选择了简单的编译器。这使得编译器更容易实现,但它会导致用户收到这样的错误消息,这(理论上)是可以避免的(在许多情况下,并非所有情况下)