1/0 является допустимым выражением Java? - PullRequest
37 голосов
/ 29 мая 2010

В моем Eclipse отлично скомпилируется следующее:

final int j = 1/0;
// compiles fine!!!
// throws ArithmeticException: / by zero at run-time

Java предотвращает компиляцию даже многих «глупых кодов» (например, "Five" instanceof Number не компилируется!), Поэтому тот факт, что это даже не вызвало столько предупреждений, меня очень удивило. Интрига становится глубже, если учесть тот факт, что константные выражения разрешено оптимизировать во время компиляции:

public class Div0 {
    public static void main(String[] args) {
        final int i = 2+3;
        final int j = 1/0;
        final int k = 9/2;
    }
}

Скомпилированный в Eclipse, приведенный выше фрагмент генерирует следующий байт-код (javap -c Div0)

Compiled from "Div0.java"
public class Div0 extends java.lang.Object{
public Div0();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_5
   1:   istore_1      // "i = 5;"
   2:   iconst_1
   3:   iconst_0
   4:   idiv
   5:   istore_2      // "j = 1/0;"
   6:   iconst_4
   7:   istore_3      // "k = 4;"
   8:   return

}

Как видите, назначения i и k оптимизированы как константы времени компиляции, но деление на 0 (которое должно было быть обнаружено во время компиляции) просто компилируется как есть.

javac 1.6.0_17 ведет себя еще более странно, компилируя молча, но вырезая назначения для i и k полностью из байт-кода (вероятно, потому что он определил, что они нигде не используются), но оставляя 1/0 без изменений (поскольку его удаление вызовет совершенно другую семантику программы).

Итак, вопросы:

  • Является ли 1/0 на самом деле допустимым выражением Java, которое должно скомпилироваться в любое время в любом месте?
    • Что JLS говорит об этом?
  • Если это законно, есть ли для этого веская причина?
    • Что хорошего это может послужить?

Ответы [ 8 ]

32 голосов
/ 29 мая 2010

Является ли 1/0 на самом деле допустимым выражением Java, которое должно компилироваться в любое время в любом месте?

Да.

Что JLS говорит об этом?

Ничего конкретного ... кроме того, что деление на ноль приведет к исключению времени выполнения. Однако JLS признает такую ​​возможность исключений времени выполнения в следующем определении:

"Постоянное выражение времени компиляции - это выражение, обозначающее значение типа примитива или String , которое не завершается внезапно и составлено с использованием только следующего: ..."

(выделение добавлено). Поэтому следующее НЕ будет компилироваться:

switch(i) {
    case 1:
    case 1 + 1: 
    case 1 / 0:  // compilation error.
}

Если это законно, есть ли для этого веская причина?

Хороший вопрос. Я полагаю, что это способ бросить ArithmeticException, хотя вряд ли это правдоподобная причина. Более вероятная причина для указания Java таким способом - избежать ненужной сложности в JLS и компиляторах, чтобы иметь дело с крайним случаем, который редко собирается кусать людей.

Но это все, кстати. Дело в том, что 1/0 является допустимым кодом Java, и ни один компилятор Java не должен помечать это как ошибку компиляции. (Было бы разумно, чтобы компилятор Java выдавал предупреждение при условии, что был выключатель компилятора для его выключения.)

20 голосов
/ 29 мая 2010

Я немного покопался в базе данных ошибок и обнаружил некоторую интересную информацию.

Идентификатор ошибки 4178182: JLS не определяет поведение для 1/0 как константное выражение

Следующий код недопустим:

class X { static final int i = 1 / 0; }

Значение этой константы времени компиляции не определено, , поэтому это должна быть ошибка во время компиляции. Гай Стил подтвердил около 18 месяцев назад, что это действительно было предполагаемое поведение.

Постоянная времени компиляции должна иметь статически доступное значение (это что делает его постоянной времени компиляции ;-) Например, значение других константы, значения которых определяются константой, содержащей деление по нулю не определены. Это влияет на семантику операторов switch, определенное назначение и назначение и т. д.

Ошибка ID 4089107: javac рассматривает целочисленное деление на (постоянное) ноль как ошибку

public class zero {
   public static void main(String[] args) {
      System.out.println(1/0);
   }
}

Запуск вышеуказанных выходов:

zero.java:3: Arithmetic exception.
     System.out.println(1/0);
                         ^
1 error

Идентификатор ошибки 4154563: javac допускает деление на ноль константных выражений в выражениях.

Сбой компилятора Java при попытке компилировать следующий тест. Этот тест также приводит к сбою всех версий компилятора 1.2beta4, но в 12.beta3 ошибка отсутствует. Пример и диагностика компилятора приведены ниже:

public class B {
   public static void main(String argv[]) {
      switch(0){
         case 0/0:
      }
  }
}

Оценка: Компилятор сообщал обо всех попытках деления на постоянный ноль как ошибки времени компиляции. Это было исправлено в бета3, так что код будет сгенерирован для деления на постоянный ноль. К сожалению, эта ошибка была введена. Компилятор должен изящно обрабатывать деление на ноль в выражении регистра.

Заключение

Так что вопрос о том или нет 1/0 компилируется была спорной темой для обсуждения, с некоторыми людьми, цитируя Гай Стил утверждает, что это должно быть компиляции ошибки времени, а другие говорят, что он не должен. Кажется, что в конечном итоге было решено, что это не ошибка времени компиляции и не константа времени компиляции.

2 голосов
/ 29 мая 2010

Что ж, если вы загляните в класс Double, вы увидите следующее:

/**
 * A constant holding the positive infinity of type
 * <code>double</code>. It is equal to the value returned by
 * <code>Double.longBitsToDouble(0x7ff0000000000000L)</code>.
 */
public static final double POSITIVE_INFINITY = 1.0 / 0.0;

То же самое вычисление сделано в классе Float, за исключением случаев с числами с плавающей точкой вместо двойных. В основном, 1/0 возвращает действительно очень большое число, больше Double.MAX_VALUE.

Этот следующий код:

public static void main(String[] args) {
    System.out.println(Double.POSITIVE_INFINITY);
    System.out.println(Double.POSITIVE_INFINITY > Double.MAX_VALUE);
}

Выходы:

Infinity
true

Обратите внимание на особый случай при распечатке Double.POSITIVE_INFINITY. Он выводит строку, хотя считается двойным.

Чтобы ответить на вопрос, да, это допустимо в Java, но 1/0 разрешается до «бесконечности» и трактуется иначе, чем стандартные двойные (или с плавающей точкой, и т. Д.)

Должен заметить, что у меня нет ни малейшего понятия, как или почему это было реализовано таким образом. Когда я вижу вышеприведенный вывод, мне кажется, что это черная магия.

2 голосов
/ 29 мая 2010

Java явно требует целочисленного деления на ноль, чтобы вызвать ArithmeticException. Назначение на j не может быть отменено, потому что это нарушит спецификацию.

0 голосов
/ 08 января 2016

Поскольку другие уже ответили на законность 1/0, давайте перейдем ко второму вопросу:

  • Если это законно, есть ли для этого веская причина?
    • Что хорошего это может послужить?

Ответ может быть:

Чтобы дразнить вашего коллегу. ; o)

Когда коллега покидает комнату, оставив свой компьютер незапертым, подкрадитесь и закопайте 1/0 где-то глубоко в статическом инициализаторе некоторого класса, который используется в начале приложения. Таким образом, он узнает достаточно скоро после (или даже во время) развертывания приложения, встретив необычный ArithmeticException, и он, вероятно, немного почесает голову. Используя этот безотказный способ, вы можете быть уверены, что это относительно безобидная шутка.

П.С .: Сработало. ; О)

0 голосов
/ 29 мая 2010

Зачем ловить это во время компиляции, если вам все равно понадобится вариант во время выполнения?

Например, если вы загружаете и анализируете «0» из текстового файла, а затем пытаетесь разделить его, Java не будет знать, что вы делали во время компиляции, потому что не знает содержимого этого. внешний файл.

Кроме того, если бы вы установили любую переменную в 0 и поделили на переменную, Java пришлось бы отслеживать каждое возможное значение каждой переменной в каждой точке скрипта, чтобы перехватить деление на 0 во время компиляции.

Возможно, все будет согласованно и станет исключением только во время выполнения.

0 голосов
/ 29 мая 2010

Это допустимо в точке зрения компиляции , но в случае выполнения оно выдаст исключение!

причина ... хорошее программирование должно обеспечивать гибкость, поэтому все выражения и каждый введенный вами код являются переменными для компилятора, поэтому в математическом выражении X / Y компилятору все равно, значение переменной Y ( Y == 0 ) или любое другое число для компилятора, это переменная ... если компилятор должен будет также посмотреть значения, это будет считаться временем выполнения, не будет т.

0 голосов
/ 29 мая 2010

Это законно, потому что нет данных, что компилятор должен складывать константные выражения во время компиляции.

«Умный» компилятор может скомпилировать:

a = 1 + 2

как

a = 3

Но ничто не говорит о том, что компилятор ДОЛЖЕН это сделать. Кроме этого, 1/0 является юридическим выражением, таким как:

int a;
int b;

a = a/b;

является юридическим выражением.

В RUNTIME выдает исключение, но это причина ошибки времени по причине.

...